From d98ac33425015ac93c35321a61e1472b01f23a29 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 2 Jun 2020 17:43:17 +0300 Subject: [PATCH] Improved WebPage preview support in scheduled text messages. --- .../view/history_view_compose_controls.cpp | 272 ++++++++++++++++-- .../view/history_view_compose_controls.h | 1 + 2 files changed, 247 insertions(+), 26 deletions(-) diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index 39b5366c70..cba9f618e5 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_compose_controls.h" +#include "base/unixtime.h" #include "data/data_web_page.h" #include "ui/widgets/input_fields.h" #include "ui/special_buttons.h" @@ -39,6 +40,22 @@ namespace { 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 class FieldHeader : public Ui::RpWidget { @@ -48,17 +65,24 @@ public: void init(); void editMessage(FullMsgId edit); + void previewRequested( + rpl::producer title, + rpl::producer description, + rpl::producer page); bool isDisplayed() const; bool isEditingMessage() const; rpl::producer editMsgId() const; MessageToEdit queryToEdit(); + rpl::producer visibleChanged(); + protected: void paintEvent(QPaintEvent *e) override; private: void updateControlsGeometry(QSize size); + void updateVisible(); struct Preview { WebPageData *data = nullptr; @@ -66,6 +90,9 @@ private: Ui::Text::String description; }; + rpl::variable _title; + rpl::variable _description; + Preview _preview; bool hasPreview() const; @@ -76,6 +103,8 @@ private: const not_null _data; const not_null _cancel; + rpl::event_stream _visibleChanged; + }; FieldHeader::FieldHeader(QWidget *parent, not_null data) @@ -96,32 +125,17 @@ void FieldHeader::init() { _preview = {}; if (const auto media = item->media()) { if (const auto page = media->webpage()) { + const auto preview = ProcessWebPageData(page); + _title = preview.title; + _description = preview.description; _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( ) | rpl::start_with_next([=] { - isDisplayed() ? show() : hide(); - + updateVisible(); if (const auto item = _data->message(_editMsgId.current())) { _editMsgText.setText( st::messageTextStyle, @@ -139,14 +153,56 @@ void FieldHeader::init() { _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 title, + rpl::producer description, + rpl::producer 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) { Painter p(this); const auto replySkip = st::historyReplySkip; - const auto drawWebPagePreview = - (hasPreview() && _preview.data->pendingTill >= 0); + const auto drawWebPagePreview = ShowWebPagePreview(_preview.data); p.fillRect(rect(), st::historyComposeAreaBg); @@ -211,8 +267,17 @@ void FieldHeader::paintEvent(QPaintEvent *e) { p.restoreTextPalette(); } +void FieldHeader::updateVisible() { + isDisplayed() ? show() : hide(); + _visibleChanged.fire(isVisible()); +} + +rpl::producer FieldHeader::visibleChanged() { + return _visibleChanged.events(); +} + bool FieldHeader::isDisplayed() const { - return isEditingMessage(); + return isEditingMessage() || hasPreview(); } bool FieldHeader::isEditingMessage() const { @@ -293,6 +358,7 @@ void ComposeControls::setHistory(History *history) { _history = history; _window->tabbedSelector()->setCurrentPeer( history ? history->peer.get() : nullptr); + initWebpageProcess(); } void ComposeControls::move(int x, int y) { @@ -443,15 +509,18 @@ void ComposeControls::init() { _header->editMsgId( ) | rpl::start_with_next([=](const auto &id) { - updateHeight(); - updateSendButtonType(); - if (_header->isEditingMessage()) { setTextFromEditingMessage(_window->session().data().message(id)); } else { setText(_localSavedText); _localSavedText = {}; } + updateSendButtonType(); + }, _wrap->lifetime()); + + _header->visibleChanged( + ) | rpl::start_with_next([=] { + updateHeight(); }, _wrap->lifetime()); { @@ -701,7 +770,9 @@ void ComposeControls::updateHeight() { const auto height = _field->height() + (_header->isDisplayed() ? _header->height() : 0) + 2 * st::historySendPadding; - _wrap->resize(_wrap->width(), height); + if (height != _wrap->height()) { + _wrap->resize(_wrap->width(), height); + } } void ComposeControls::editMessage(FullMsgId edit) { @@ -713,4 +784,153 @@ void ComposeControls::cancelEditMessage() { _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(); + const auto previewLinks = lifetime.make_state(); + const auto previewData = lifetime.make_state(nullptr); + using PreviewCache = std::map; + const auto previewCache = lifetime.make_state(); + const auto previewRequest = lifetime.make_state(0); + const auto previewCancelled = lifetime.make_state(false); + const auto mtpSender = + lifetime.make_state(&_window->session().mtp()); + + const auto title = std::make_shared>(); + const auto description = std::make_shared>(); + const auto pageData = std::make_shared>(); + + const auto previewTimer = lifetime.make_state(); + + 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() + )).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(_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 diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/history_view_compose_controls.h index 99019c0331..7e47e310f5 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.h @@ -112,6 +112,7 @@ private: void initField(); void initTabbedSelector(); void initSendButton(); + void initWebpageProcess(); void updateSendButtonType(); void updateHeight(); void updateControlsGeometry(QSize size);