Improved WebPage preview support in scheduled text messages.

This commit is contained in:
23rd 2020-06-02 17:43:17 +03:00
parent 76842792b8
commit d98ac33425
2 changed files with 247 additions and 26 deletions

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/view/history_view_compose_controls.h" #include "history/view/history_view_compose_controls.h"
#include "base/unixtime.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/special_buttons.h" #include "ui/special_buttons.h"
@ -39,6 +40,22 @@ namespace {
using MessageToEdit = ComposeControls::MessageToEdit; using MessageToEdit = ComposeControls::MessageToEdit;
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
return page && (page->pendingTill >= 0);
}
WebPageText ProcessWebPageData(WebPageData *page) {
auto previewText = HistoryView::TitleAndDescriptionFromWebPage(page);
if (previewText.title.isEmpty()) {
if (page->document) {
previewText.title = tr::lng_attach_file(tr::now);
} else if (page->photo) {
previewText.title = tr::lng_attach_photo(tr::now);
}
}
return previewText;
}
} // namespace } // namespace
class FieldHeader : public Ui::RpWidget { class FieldHeader : public Ui::RpWidget {
@ -48,17 +65,24 @@ public:
void init(); void init();
void editMessage(FullMsgId edit); void editMessage(FullMsgId edit);
void previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page);
bool isDisplayed() const; bool isDisplayed() const;
bool isEditingMessage() const; bool isEditingMessage() const;
rpl::producer<FullMsgId> editMsgId() const; rpl::producer<FullMsgId> editMsgId() const;
MessageToEdit queryToEdit(); MessageToEdit queryToEdit();
rpl::producer<bool> visibleChanged();
protected: protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
private: private:
void updateControlsGeometry(QSize size); void updateControlsGeometry(QSize size);
void updateVisible();
struct Preview { struct Preview {
WebPageData *data = nullptr; WebPageData *data = nullptr;
@ -66,6 +90,9 @@ private:
Ui::Text::String description; Ui::Text::String description;
}; };
rpl::variable<QString> _title;
rpl::variable<QString> _description;
Preview _preview; Preview _preview;
bool hasPreview() const; bool hasPreview() const;
@ -76,6 +103,8 @@ private:
const not_null<Data::Session*> _data; const not_null<Data::Session*> _data;
const not_null<Ui::IconButton*> _cancel; const not_null<Ui::IconButton*> _cancel;
rpl::event_stream<bool> _visibleChanged;
}; };
FieldHeader::FieldHeader(QWidget *parent, not_null<Data::Session*> data) FieldHeader::FieldHeader(QWidget *parent, not_null<Data::Session*> data)
@ -96,32 +125,17 @@ void FieldHeader::init() {
_preview = {}; _preview = {};
if (const auto media = item->media()) { if (const auto media = item->media()) {
if (const auto page = media->webpage()) { if (const auto page = media->webpage()) {
const auto preview = ProcessWebPageData(page);
_title = preview.title;
_description = preview.description;
_preview.data = page; _preview.data = page;
auto preview =
HistoryView::TitleAndDescriptionFromWebPage(page);
if (preview.title.isEmpty()) {
if (page->document) {
preview.title = tr::lng_attach_file(tr::now);
} else if (page->photo) {
preview.title = tr::lng_attach_photo(tr::now);
}
}
_preview.title.setText(
st::msgNameStyle,
preview.title,
Ui::NameTextOptions());
_preview.description.setText(
st::messageTextStyle,
TextUtilities::Clean(preview.description),
Ui::DialogTextOptions());
} }
} }
}; };
_editMsgId.value( _editMsgId.value(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
isDisplayed() ? show() : hide(); updateVisible();
if (const auto item = _data->message(_editMsgId.current())) { if (const auto item = _data->message(_editMsgId.current())) {
_editMsgText.setText( _editMsgText.setText(
st::messageTextStyle, st::messageTextStyle,
@ -139,14 +153,56 @@ void FieldHeader::init() {
_editMsgId = {}; _editMsgId = {};
} }
}); });
_title.value(
) | rpl::start_with_next([=](const auto &t) {
_preview.title.setText(
st::msgNameStyle,
t,
Ui::NameTextOptions());
}, lifetime());
_description.value(
) | rpl::start_with_next([=](const auto &d) {
_preview.description.setText(
st::messageTextStyle,
TextUtilities::Clean(d),
Ui::DialogTextOptions());
}, lifetime());
}
void FieldHeader::previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page) {
std::move(
title
) | rpl::start_with_next([=](const QString &t) {
_title = t;
}, lifetime());
std::move(
description
) | rpl::start_with_next([=](const QString &d) {
_description = d;
}, lifetime());
std::move(
page
) | rpl::start_with_next([=](WebPageData *p) {
_preview.data = p;
updateVisible();
}, lifetime());
} }
void FieldHeader::paintEvent(QPaintEvent *e) { void FieldHeader::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
const auto replySkip = st::historyReplySkip; const auto replySkip = st::historyReplySkip;
const auto drawWebPagePreview = const auto drawWebPagePreview = ShowWebPagePreview(_preview.data);
(hasPreview() && _preview.data->pendingTill >= 0);
p.fillRect(rect(), st::historyComposeAreaBg); p.fillRect(rect(), st::historyComposeAreaBg);
@ -211,8 +267,17 @@ void FieldHeader::paintEvent(QPaintEvent *e) {
p.restoreTextPalette(); p.restoreTextPalette();
} }
void FieldHeader::updateVisible() {
isDisplayed() ? show() : hide();
_visibleChanged.fire(isVisible());
}
rpl::producer<bool> FieldHeader::visibleChanged() {
return _visibleChanged.events();
}
bool FieldHeader::isDisplayed() const { bool FieldHeader::isDisplayed() const {
return isEditingMessage(); return isEditingMessage() || hasPreview();
} }
bool FieldHeader::isEditingMessage() const { bool FieldHeader::isEditingMessage() const {
@ -293,6 +358,7 @@ void ComposeControls::setHistory(History *history) {
_history = history; _history = history;
_window->tabbedSelector()->setCurrentPeer( _window->tabbedSelector()->setCurrentPeer(
history ? history->peer.get() : nullptr); history ? history->peer.get() : nullptr);
initWebpageProcess();
} }
void ComposeControls::move(int x, int y) { void ComposeControls::move(int x, int y) {
@ -443,15 +509,18 @@ void ComposeControls::init() {
_header->editMsgId( _header->editMsgId(
) | rpl::start_with_next([=](const auto &id) { ) | rpl::start_with_next([=](const auto &id) {
updateHeight();
updateSendButtonType();
if (_header->isEditingMessage()) { if (_header->isEditingMessage()) {
setTextFromEditingMessage(_window->session().data().message(id)); setTextFromEditingMessage(_window->session().data().message(id));
} else { } else {
setText(_localSavedText); setText(_localSavedText);
_localSavedText = {}; _localSavedText = {};
} }
updateSendButtonType();
}, _wrap->lifetime());
_header->visibleChanged(
) | rpl::start_with_next([=] {
updateHeight();
}, _wrap->lifetime()); }, _wrap->lifetime());
{ {
@ -701,7 +770,9 @@ void ComposeControls::updateHeight() {
const auto height = _field->height() const auto height = _field->height()
+ (_header->isDisplayed() ? _header->height() : 0) + (_header->isDisplayed() ? _header->height() : 0)
+ 2 * st::historySendPadding; + 2 * st::historySendPadding;
_wrap->resize(_wrap->width(), height); if (height != _wrap->height()) {
_wrap->resize(_wrap->width(), height);
}
} }
void ComposeControls::editMessage(FullMsgId edit) { void ComposeControls::editMessage(FullMsgId edit) {
@ -713,4 +784,153 @@ void ComposeControls::cancelEditMessage() {
_header->editMessage({}); _header->editMessage({});
} }
void ComposeControls::initWebpageProcess() {
const auto peer = _history->peer;
auto &lifetime = _wrap->lifetime();
const auto requestRepaint = crl::guard(_header.get(), [=] {
_header->update();
});
const auto parsedLinks = lifetime.make_state<QStringList>();
const auto previewLinks = lifetime.make_state<QString>();
const auto previewData = lifetime.make_state<WebPageData*>(nullptr);
using PreviewCache = std::map<QString, WebPageId>;
const auto previewCache = lifetime.make_state<PreviewCache>();
const auto previewRequest = lifetime.make_state<mtpRequestId>(0);
const auto previewCancelled = lifetime.make_state<bool>(false);
const auto mtpSender =
lifetime.make_state<MTP::Sender>(&_window->session().mtp());
const auto title = std::make_shared<rpl::event_stream<QString>>();
const auto description = std::make_shared<rpl::event_stream<QString>>();
const auto pageData = std::make_shared<rpl::event_stream<WebPageData*>>();
const auto previewTimer = lifetime.make_state<base::Timer>();
const auto updatePreview = [=] {
previewTimer->cancel();
auto t = QString();
auto d = QString();
if (ShowWebPagePreview(*previewData)) {
// updateMouseTracking();
if (const auto till = (*previewData)->pendingTill) {
t = tr::lng_preview_loading(tr::now);
d = (*previewLinks).splitRef(' ').at(0).toString();
const auto timeout = till - base::unixtime::now();
previewTimer->callOnce(
std::max(timeout, 0) * crl::time(1000));
} else {
const auto preview = ProcessWebPageData(*previewData);
t = preview.title;
d = preview.description;
}
}
title->fire_copy(t);
description->fire_copy(d);
pageData->fire_copy(*previewData);
requestRepaint();
};
const auto gotPreview = crl::guard(_wrap.get(), [=](
const auto &result,
QString links) {
if (*previewRequest) {
*previewRequest = 0;
}
result.match([=](const MTPDmessageMediaWebPage &d) {
const auto page = _history->owner().processWebpage(d.vwebpage());
previewCache->insert({ links, page->id });
auto &till = page->pendingTill;
if (till > 0 && till <= base::unixtime::now()) {
till = -1;
}
if (links == *previewLinks && !*previewCancelled) {
*previewData = (page->id && page->pendingTill >= 0)
? page.get()
: nullptr;
updatePreview();
}
}, [=](const MTPDmessageMediaEmpty &d) {
previewCache->insert({ links, 0 });
if (links == *previewLinks && !*previewCancelled) {
*previewData = nullptr;
updatePreview();
}
}, [](const auto &d) {
});
});
const auto previewCancel = [=] {
mtpSender->request(base::take(*previewRequest)).cancel();
*previewData = nullptr;
previewLinks->clear();
updatePreview();
};
const auto getWebPagePreview = [=] {
const auto links = *previewLinks;
*previewRequest = mtpSender->request(MTPmessages_GetWebPagePreview(
MTP_flags(0),
MTP_string(links),
MTPVector<MTPMessageEntity>()
)).done([=](const MTPMessageMedia &result) {
gotPreview(result, links);
}).send();
};
const auto checkPreview = crl::guard(_wrap.get(), [=] {
const auto previewRestricted = peer
&& peer->amRestricted(ChatRestriction::f_embed_links);
if (/*_previewCancelled ||*/ previewRestricted) {
previewCancel();
return;
}
const auto newLinks = parsedLinks->join(' ');
if (*previewLinks == newLinks) {
return;
}
mtpSender->request(base::take(*previewRequest)).cancel();
*previewLinks = newLinks;
if (previewLinks->isEmpty()) {
if (ShowWebPagePreview(*previewData)) {
previewCancel();
}
} else {
const auto i = previewCache->find(*previewLinks);
if (i == previewCache->end()) {
getWebPagePreview();
} else if (i->second) {
*previewData = _history->owner().webpage(i->second);
updatePreview();
} else if (ShowWebPagePreview(*previewData)) {
previewCancel();
}
}
});
previewTimer->setCallback([=] {
if (!ShowWebPagePreview(*previewData) || previewLinks->isEmpty()) {
return;
}
getWebPagePreview();
});
const auto fieldLinksParser =
lifetime.make_state<MessageLinksParser>(_field);
fieldLinksParser->list().changes(
) | rpl::start_with_next([=](QStringList &&parsed) {
*parsedLinks = std::move(parsed);
checkPreview();
}, lifetime);
_header->previewRequested(
title->events(),
description->events(),
pageData->events());
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -112,6 +112,7 @@ private:
void initField(); void initField();
void initTabbedSelector(); void initTabbedSelector();
void initSendButton(); void initSendButton();
void initWebpageProcess();
void updateSendButtonType(); void updateSendButtonType();
void updateHeight(); void updateHeight();
void updateControlsGeometry(QSize size); void updateControlsGeometry(QSize size);