From 17578be4b94b4344b567329a50de1503e377a0ae Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 25 Oct 2023 21:23:54 +0400 Subject: [PATCH] Edit reply / webpage options together. --- Telegram/CMakeLists.txt | 6 +- Telegram/SourceFiles/data/data_drafts.cpp | 1 + Telegram/SourceFiles/data/data_web_page.cpp | 27 + Telegram/SourceFiles/data/data_web_page.h | 1 + .../history/history_inner_widget.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 376 +++----- Telegram/SourceFiles/history/history_widget.h | 29 +- .../history_view_compose_controls.cpp | 405 ++------- .../controls/history_view_compose_controls.h | 12 +- .../controls/history_view_draft_options.cpp | 846 ++++++++++++++++++ ...options.h => history_view_draft_options.h} | 11 +- .../controls/history_view_reply_options.cpp | 535 ----------- .../history_view_webpage_processor.cpp | 327 +++++++ .../controls/history_view_webpage_processor.h | 100 +++ .../view/history_view_replies_section.cpp | 2 +- .../view/media/history_view_web_page.cpp | 25 +- Telegram/lib_ui | 2 +- 17 files changed, 1525 insertions(+), 1182 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp rename Telegram/SourceFiles/history/view/controls/{history_view_reply_options.h => history_view_draft_options.h} (80%) delete mode 100644 Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp create mode 100644 Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d43e91d1f..32aa477b4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -651,16 +651,18 @@ PRIVATE history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.h + history/view/controls/history_view_draft_options.cpp + history/view/controls/history_view_draft_options.h history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.h - history/view/controls/history_view_reply_options.cpp - history/view/controls/history_view_reply_options.h history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.h history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.h history/view/controls/history_view_voice_record_button.cpp history/view/controls/history_view_voice_record_button.h + history/view/controls/history_view_webpage_processor.cpp + history/view/controls/history_view_webpage_processor.h history/view/media/history_view_call.cpp history/view/media/history_view_call.h history/view/media/history_view_contact.cpp diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index adf92511b..6f386e582 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -37,6 +37,7 @@ WebPageDraft WebPageDraft::FromItem(not_null item) { .forceSmallMedia = !!(previewFlags & PageFlag::ForceSmallMedia), .invert = item->invertMedia(), .manual = !!(previewFlags & PageFlag::Manual), + .removed = !previewPage, }; } diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 2e473f347..ec0f96454 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -355,3 +355,30 @@ QString WebPageData::displayedSiteName() const { ? tr::lng_media_color_theme(tr::now) : siteName; } + +bool WebPageData::computeDefaultSmallMedia() const { + if (!collage.items.empty()) { + return false; + } else if (siteName.isEmpty() + && title.isEmpty() + && description.empty() + && author.isEmpty()) { + return false; + } else if (!document + && photo + && type != WebPageType::Photo + && type != WebPageType::Document + && type != WebPageType::Story + && type != WebPageType::Video) { + if (type == WebPageType::Profile) { + return true; + } else if (siteName == u"Twitter"_q + || siteName == u"Facebook"_q + || type == WebPageType::ArticleWithIV) { + return false; + } else { + return true; + } + } + return false; +} diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index a498a5767..9aea4a0f5 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -89,6 +89,7 @@ struct WebPageData { const MTPmessages_Messages &result); [[nodiscard]] QString displayedSiteName() const; + [[nodiscard]] bool computeDefaultSmallMedia() const; const WebPageId id = 0; WebPageType type = WebPageType::None; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c6440fce3..e0944e7a8 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/media/history_view_media.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 836c36892..9a595520b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -82,9 +82,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" +#include "history/view/controls/history_view_webpage_processor.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_service_message.h" @@ -206,7 +207,6 @@ HistoryWidget::HistoryWidget( , _api(&controller->session().mtp()) , _updateEditTimeLeftDisplay([=] { updateField(); }) , _fieldBarCancel(this, st::historyReplyCancel) -, _previewTimer([=] { requestPreview(); }) , _topBar(this, controller) , _scroll( this, @@ -440,12 +440,6 @@ HistoryWidget::HistoryWidget( if (_supportAutocomplete) { supportInitAutocomplete(); } - _fieldLinksParser = std::make_unique(_field); - _fieldLinksParser->list().changes( - ) | rpl::start_with_next([=](QStringList &&parsed) { - _parsedLinks = std::move(parsed); - checkPreview(); - }, lifetime()); _field->rawTextEdit()->installEventFilter(this); _field->rawTextEdit()->installEventFilter(_fieldAutocomplete); _field->setMimeDataHook([=]( @@ -566,13 +560,6 @@ HistoryWidget::HistoryWidget( }); }, lifetime()); - session().data().webPageUpdates( - ) | rpl::filter([=](not_null page) { - return (_previewData == page.get()); - }) | rpl::start_with_next([=] { - updatePreview(); - }, lifetime()); - session().data().channelDifferenceTooLong( ) | rpl::filter([=](not_null channel) { return _peer == channel.get(); @@ -723,9 +710,9 @@ HistoryWidget::HistoryWidget( return update.flags; }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & PeerUpdateFlag::Rights) { - checkPreview(); updateStickersByEmoji(); updateFieldPlaceholder(); + _preview->checkNow(false); } if (flags & PeerUpdateFlag::Migration) { handlePeerMigration(); @@ -1600,7 +1587,9 @@ void HistoryWidget::fieldChanged() { updateSendButtonType(); if (!HasSendText(_field)) { - _previewDraft = {}; + if (_preview) { + _preview->apply({}); + } _fieldIsEmpty = true; } else if (_fieldIsEmpty) { _fieldIsEmpty = false; @@ -1664,14 +1653,14 @@ void HistoryWidget::saveFieldToHistoryLocalDraft() { .messageId = FullMsgId(_history->peer->id, _editMsgId), .topicRootId = topicRootId, }, - _previewDraft, + _preview->draft(), _saveEditMsgRequestId)); } else { if (_replyTo || !_field->empty()) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewDraft)); + _preview->draft())); } else { _history->clearLocalDraft(topicRootId); } @@ -1897,6 +1886,9 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { _processingReplyItem = _replyEditMsg = nullptr; _processingReplyTo = _replyTo = FullReplyTo(); setEditMsgId(0); + if (_preview) { + _preview->apply({}); + } if (fieldWillBeHiddenAfterEdit) { updateControlsVisibility(); updateControlsGeometry(); @@ -1930,18 +1922,9 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { processReply(); } - // Save links from _field to _parsedLinks without generating preview. - _previewDraft = { .removed = true }; - if (_editMsgId) { - _fieldLinksParser->setDisabled(!_replyEditMsg - || (_replyEditMsg->media() - && !_replyEditMsg->media()->webpage())); + if (_preview) { + _preview->apply(draft->webpage, !_editMsgId); } - _fieldLinksParser->parseNow(); - _parsedLinks = _fieldLinksParser->list().current(); - _previewDraft = draft->webpage; - checkPreview(); - return true; } @@ -2149,9 +2132,6 @@ void HistoryWidget::showHistory( _canReplaceMedia = false; _photoEditMedia = nullptr; updateReplaceMediaButton(); - _previewData = nullptr; - _previewDraft = {}; - _previewCache.clear(); _fieldBarCancel->hide(); _membersDropdownShowTimer.cancel(); @@ -2409,10 +2389,45 @@ void HistoryWidget::setHistory(History *history) { _history = history; _migrated = _history ? _history->migrateFrom() : nullptr; registerDraftSource(); + if (_history) { + setupPreview(); + } else { + _previewDrawPreview = nullptr; + _preview = nullptr; + } } refreshAttachBotsMenu(); } +void HistoryWidget::setupPreview() { + Expects(_history != nullptr); + + using namespace HistoryView::Controls; + _preview = std::make_unique(_history, _field); + _preview->repaintRequests() | rpl::start_with_next([=] { + updateField(); + }, _preview->lifetime()); + + _preview->parsedValue( + ) | rpl::start_with_next([=](WebpageParsed value) { + _previewTitle.setText( + st::msgNameStyle, + value.title, + Ui::NameTextOptions()); + _previewDescription.setText( + st::defaultTextStyle, + value.description, + Ui::DialogTextOptions()); + const auto changed = (!_previewDrawPreview != !value.drawPreview); + _previewDrawPreview = value.drawPreview; + if (changed) { + updateControlsGeometry(); + updateControlsVisibility(); + } + updateField(); + }, _preview->lifetime()); +} + void HistoryWidget::injectSponsoredMessages() const { session().data().sponsoredMessages().inject( _history, @@ -2468,7 +2483,7 @@ void HistoryWidget::registerDraftSource() { ? FullReplyTo{ FullMsgId(peerId, editMsgId) } : _replyTo), _field->getTextWithTags(), - _previewDraft, + _preview->draft(), }; }; auto draftSource = Storage::MessageDraftSource{ @@ -2486,9 +2501,6 @@ void HistoryWidget::registerDraftSource() { void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; - if (_fieldLinksParser && !_editMsgId) { - _fieldLinksParser->setDisabled(false); - } if (!msgId) { _canReplaceMedia = false; } @@ -2906,7 +2918,7 @@ void HistoryWidget::updateControlsVisibility() { if (_editMsgId || _replyTo || readyToForward() - || (_previewData && !_previewData->failed) + || _previewDrawPreview || _kbReplyTo) { if (_fieldBarCancel->isHidden()) { _fieldBarCancel->show(); @@ -3832,7 +3844,7 @@ void HistoryWidget::saveEditMsg() { _saveEditMsgRequestId = Api::EditTextMessage( item, sending, - _previewDraft, + _preview->draft(), options, done, fail); @@ -3918,7 +3930,7 @@ void HistoryWidget::send(Api::SendOptions options) { auto message = Api::MessageToSend(prepareSendAction(options)); message.textWithTags = _field->getTextWithAppliedMarkdown(); - message.webPage = _previewDraft; + message.webPage = _preview->draft(); const auto ignoreSlowmodeCountdown = (options.scheduled != 0); if (showSendMessageError( @@ -3935,9 +3947,6 @@ void HistoryWidget::send(Api::SendOptions options) { hideSelectorControlsAnimated(); - if (_previewData && _previewData->pendingTill) { - previewCancel(); - } setInnerFocus(); if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) { @@ -4333,7 +4342,7 @@ void HistoryWidget::updateOverStates(QPoint pos) { _field->y() - st::historySendPadding - st::historyReplyHeight, width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - const auto hasWebPage = _previewData && !_previewData->failed; + const auto hasWebPage = !!_previewDrawPreview; const auto inDetails = detailsRect.contains(pos) && (_editMsgId || replyTo() || isReadyToForward || hasWebPage); const auto inPhotoEdit = inDetails @@ -4595,9 +4604,7 @@ bool HistoryWidget::showRecordButton() const { && !_voiceRecordBar->isListenState() && !_voiceRecordBar->isRecordingByAnotherBar() && !HasSendText(_field) - && (!_previewData - || _previewData->failed - || _previewData->pendingTill) + && !_previewDrawPreview && !readyToForward() && !_editMsgId; } @@ -4781,7 +4788,7 @@ void HistoryWidget::toggleKeyboard(bool manual) { _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_editMsgId && !_replyTo) { _fieldBarCancel->hide(); @@ -5771,7 +5778,7 @@ void HistoryWidget::updateHistoryGeometry( if (_editMsgId || replyTo() || readyToForward() - || (_previewData && !_previewData->failed)) { + || _previewDrawPreview) { newScrollHeight -= st::historyReplyHeight; } if (_kbShown) { @@ -6074,7 +6081,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbShown = false; _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_replyTo) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -6092,7 +6099,7 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) { _kbShown = false; _kbReplyTo = nullptr; if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_replyTo && !_editMsgId) { _fieldBarCancel->hide(); @@ -6139,7 +6146,7 @@ int HistoryWidget::computeMaxFieldHeight() const { - ((_editMsgId || replyTo() || readyToForward() - || (_previewData && !_previewData->failed)) + || _previewDrawPreview) ? st::historyReplyHeight : 0) - (2 * st::historySendPadding) @@ -6219,44 +6226,21 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { crl::guard(_list, [=] { cancelEdit(); })); } else if (!_inDetails) { return; - } else if (_previewData - && !_previewData->failed - && !_previewData->pendingTill) { - const auto history = _history; - using namespace HistoryView::Controls; - EditWebPageOptions( - controller()->uiShow(), - _previewData, - _previewDraft, - [=](Data::WebPageDraft draft) { applyPreview(draft); }); + } else if (_previewDrawPreview) { + editDraftOptions(); } else if (isReadyToForward) { if (e->button() != Qt::LeftButton) { _forwardPanel->editToNextOption(); } else { _forwardPanel->editOptions(controller()->uiShow()); } - } else if (const auto reply = replyTo()) { - const auto done = [=](FullReplyTo replyTo) { - if (replyTo) { - replyToMessage(replyTo); - } else { - cancelReply(); - } - }; - const auto highlight = [=] { - controller()->showPeerHistory( - reply.messageId.peer, - Window::SectionShow::Way::Forward, - reply.messageId.msg); - }; - const auto history = _history; - using namespace HistoryView::Controls; - EditReplyOptions( - controller()->uiShow(), - reply, - done, - highlight, - [=] { ClearDraftReplyTo(history, reply.messageId); }); + } else if (_replyTo) { + editDraftOptions(); + } else if (_kbReplyTo) { + controller()->showPeerHistory( + _kbReplyTo->history()->peer->id, + Window::SectionShow::Way::Forward, + _kbReplyTo->id); } else if (_editMsgId) { controller()->showPeerHistory( _peer, @@ -6265,6 +6249,42 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { } } +void HistoryWidget::editDraftOptions() { + Expects(_history != nullptr); + + const auto history = _history; + const auto reply = _replyTo; + const auto webpage = _preview->draft(); + + const auto done = [=]( + FullReplyTo replyTo, + Data::WebPageDraft webpage) { + if (replyTo) { + replyToMessage(replyTo); + } else { + cancelReply(); + } + if (_preview->draft() != webpage) { + _preview->apply(webpage); + } + }; + const auto highlight = [=] { + controller()->showPeerHistory( + reply.messageId.peer, + Window::SectionShow::Way::Forward, + reply.messageId.msg); + }; + + using namespace HistoryView::Controls; + EditDraftOptions( + controller()->uiShow(), + history, + Data::Draft(_field, reply, _preview->draft()), + done, + highlight, + [=] { ClearDraftReplyTo(history, reply.messageId); }); +} + void HistoryWidget::keyPressEvent(QKeyEvent *e) { if (!_history) return; @@ -7075,9 +7095,8 @@ void HistoryWidget::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; - if (!_previewDraft.manual) { - previewCancel(); - _previewDraft = {}; + if (_preview) { + _preview->checkNow(false); } } @@ -7239,7 +7258,7 @@ void HistoryWidget::editMessage(not_null item) { _history->setLocalDraft(std::make_unique( _field, _replyTo, - _previewDraft)); + _preview->draft())); } else { _history->clearLocalDraft({}); } @@ -7259,13 +7278,6 @@ void HistoryWidget::editMessage(not_null item) { previewDraft)); applyDraft(); - _previewData = previewDraft.id - ? session().data().webpage(previewDraft.id).get() - : nullptr; - if (_previewData) { - updatePreview(); - } - updateBotKeyboard(); if (fieldOrDisabledShown()) { @@ -7332,7 +7344,7 @@ bool HistoryWidget::cancelReply(bool lastKeyboardUsed) { _processingReplyTo = _replyTo = FullReplyTo(); mouseMoveEvent(0); if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !_kbReplyTo) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -7405,7 +7417,7 @@ void HistoryWidget::cancelEdit() { mouseMoveEvent(nullptr); if (!readyToForward() - && (!_previewData || _previewData->failed) + && !_previewDrawPreview && !replyTo()) { _fieldBarCancel->hide(); updateMouseTracking(); @@ -7427,8 +7439,8 @@ void HistoryWidget::cancelEdit() { void HistoryWidget::cancelFieldAreaState() { controller()->hideLayer(); _replyForwardPressed = false; - if (_previewData && !_previewData->failed) { - applyPreview({ .removed = true }); + if (_previewDrawPreview) { + _preview->apply({ .removed = true }); } else if (_editMsgId) { cancelEdit(); } else if (readyToForward()) { @@ -7440,165 +7452,6 @@ void HistoryWidget::cancelFieldAreaState() { } } -void HistoryWidget::applyPreview(Data::WebPageDraft draft) { - _previewDraft = draft; - if (draft.removed) { - previewCancel(); - } else if (draft.id) { - _previewData = session().data().webpage(draft.id).get(); - requestPreview(); - } - - _saveDraftText = true; - _saveDraftStart = crl::now(); - saveDraft(); -} - -void HistoryWidget::previewCancel() { - _api.request(base::take(_previewRequest)).cancel(); - _previewData = nullptr; - _previewLinks.clear(); - updatePreview(); -} - -void HistoryWidget::checkPreview() { - const auto previewRestricted = [&] { - return _peer && _peer->amRestricted(ChatRestriction::EmbedLinks); - }(); - if (_previewDraft.removed || previewRestricted) { - previewCancel(); - return; - } else if (_previewDraft.manual) { - return; - } - const auto links = _parsedLinks.join(' '); - if (_previewLinks != links) { - _api.request(base::take(_previewRequest)).cancel(); - _previewLinks = links; - if (_previewLinks.isEmpty()) { - if (_previewData && !_previewData->failed) { - previewCancel(); - } - } else { - const auto i = _previewCache.constFind(links); - if (i == _previewCache.cend()) { - _previewRequest = _api.request(MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { - gotPreview(links, result, requestId); - }).send(); - } else if (i.value()) { - _previewData = session().data().webpage(i.value()); - _previewDraft.id = _previewData->id; - _previewDraft.url = _previewData->url; - updatePreview(); - } else if (_previewData && !_previewData->failed) { - previewCancel(); - } - } - } -} - -void HistoryWidget::requestPreview() { - if (!_previewData || _previewData->failed || _previewLinks.isEmpty()) { - return; - } - const auto links = _previewLinks; - _previewRequest = _api.request(MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { - gotPreview(links, result, requestId); - }).send(); -} - -void HistoryWidget::gotPreview( - QString links, - const MTPMessageMedia &result, - mtpRequestId req) { - if (req == _previewRequest) { - _previewRequest = 0; - } - if (result.type() == mtpc_messageMediaWebPage) { - const auto &data = result.c_messageMediaWebPage().vwebpage(); - const auto page = session().data().processWebpage(data); - _previewCache.insert(links, page->id); - if (page->pendingTill > 0 - && page->pendingTill <= base::unixtime::now()) { - page->pendingTill = 0; - page->failed = true; - } - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = (page->id && !page->failed) - ? page.get() - : nullptr; - if (_previewData) { - _previewDraft.id = _previewData->id; - _previewDraft.url = _previewData->url; - } else { - _previewDraft = {}; - } - updatePreview(); - } - session().data().sendWebPageGamePollNotifications(); - } else if (result.type() == mtpc_messageMediaEmpty) { - _previewCache.insert(links, 0); - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = nullptr; - _previewDraft = {}; - updatePreview(); - } - } -} - -void HistoryWidget::updatePreview() { - _previewTimer.cancel(); - if (_previewData && !_previewData->failed) { - _fieldBarCancel->show(); - updateMouseTracking(); - if (_previewData->pendingTill) { - _previewTitle.setText( - st::msgNameStyle, - tr::lng_preview_loading(tr::now), - Ui::NameTextOptions()); - auto linkText = QStringView(_previewLinks).split(' ').at(0).toString(); - _previewDescription.setText( - st::defaultTextStyle, - linkText, - Ui::DialogTextOptions()); - - const auto timeout = (_previewData->pendingTill - base::unixtime::now()); - _previewTimer.callOnce(std::max(timeout, 0) * crl::time(1000)); - } else { - auto preview = - HistoryView::TitleAndDescriptionFromWebPage(_previewData); - if (preview.title.isEmpty()) { - if (_previewData->document) { - preview.title = tr::lng_attach_file(tr::now); - } else if (_previewData->photo) { - preview.title = tr::lng_attach_photo(tr::now); - } - } - _previewTitle.setText( - st::msgNameStyle, - preview.title, - Ui::NameTextOptions()); - _previewDescription.setText( - st::defaultTextStyle, - preview.description, - Ui::DialogTextOptions()); - } - } else if (!readyToForward() && !replyTo() && !_editMsgId) { - _fieldBarCancel->hide(); - updateMouseTracking(); - } - updateControlsGeometry(); - update(); -} - void HistoryWidget::fullInfoUpdated() { auto refresh = false; if (_list) { @@ -7960,12 +7813,11 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { } else if (hasForward) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; - } else if (_previewData && !_previewData->failed) { + } else if (_previewDrawPreview) { backy -= st::historyReplyHeight; backh += st::historyReplyHeight; } - auto drawWebPagePreview = (_previewData && !_previewData->failed) - && !_replyForwardPressed; + auto drawWebPagePreview = _previewDrawPreview && !_replyForwardPressed; p.setInactive( controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg); @@ -8072,7 +7924,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { backy + (st::historyReplyHeight - st::historyReplyPreview) / 2, st::historyReplyPreview, st::historyReplyPreview); - if (HistoryView::DrawWebPageDataPreview(p, _previewData, _peer, to)) { + if (_previewDrawPreview(p, to)) { previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } p.setPen(st::historyReplyNameFg); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1e30935b6..a78875e22 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -94,13 +94,15 @@ class Element; class PinnedTracker; class TranslateBar; class ComposeSearch; -namespace Controls { +} // namespace HistoryView + +namespace HistoryView::Controls { class RecordLock; class VoiceRecordBar; class ForwardPanel; class TTLButton; -} // namespace Controls -} // namespace HistoryView +class WebpageProcessor; +} // namespace HistoryView::Controls class BotKeyboard; class HistoryInner; @@ -200,9 +202,6 @@ public: [[nodiscard]] QVector replyReturns() const; void setReplyReturns(PeerId peer, QVector replyReturns); - void updatePreview(); - void previewCancel(); - void escape(); void sendBotCommand(const Bot::SendCommandRequest &request); @@ -406,7 +405,6 @@ private: void startBotCommand(); void hidePinnedMessage(); void cancelFieldAreaState(); - void applyPreview(Data::WebPageDraft draft); void unblockUser(); void sendBotStartCommand(); void joinChannel(); @@ -540,9 +538,9 @@ private: void saveEditMsg(); - void checkPreview(); - void requestPreview(); - void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); + void setupPreview(); + void editDraftOptions(); + void messagesReceived(not_null peer, const MTPmessages_Messages &messages, int requestId); void messagesFailed(const MTP::Error &error, int requestId); void addMessagesToFront(not_null peer, const QVector &messages); @@ -674,16 +672,10 @@ private: mtpRequestId _saveEditMsgRequestId = 0; - QStringList _parsedLinks; - QString _previewLinks; - WebPageData *_previewData = nullptr; - typedef QMap PreviewCache; - PreviewCache _previewCache; - mtpRequestId _previewRequest = 0; + std::unique_ptr _preview; + Fn _previewDrawPreview; Ui::Text::String _previewTitle; Ui::Text::String _previewDescription; - base::Timer _previewTimer; - Data::WebPageDraft _previewDraft; bool _replyForwardPressed = false; @@ -725,7 +717,6 @@ private: const object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; - std::unique_ptr _fieldLinksParser; UserData *_inlineBot = nullptr; QString _inlineBotUsername; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index c94e0fa10..9150c5e50 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -47,9 +47,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/controls/history_view_ttl_button.h" +#include "history/view/controls/history_view_webpage_processor.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/bot_attach_web_view.h" #include "inline_bots/inline_results_widget.h" @@ -120,222 +121,6 @@ WebPageText ProcessWebPageData(WebPageData *page) { } // namespace -class WebpageProcessor final { -public: - WebpageProcessor( - not_null history, - not_null field); - - void cancel(); - void checkPreview(); - - [[nodiscard]] Data::WebPageDraft draft() const; - void setAllowed(bool allowed); - void refreshDraft(Data::WebPageDraft draft, bool disable); - - [[nodiscard]] rpl::producer<> paintRequests() const; - [[nodiscard]] rpl::producer titleChanges() const; - [[nodiscard]] rpl::producer descriptionChanges() const; - [[nodiscard]] rpl::producer pageDataChanges() const; - -private: - void updatePreview(); - void getWebPagePreview(); - - const not_null _history; - MTP::Sender _api; - MessageLinksParser _fieldLinksParser; - - Data::WebPageDraft _previewDraft; - - QStringList _parsedLinks; - QString _previewLinks; - - WebPageData *_previewData = nullptr; - std::map _previewCache; - - mtpRequestId _previewRequest = 0; - - rpl::event_stream<> _paintRequests; - rpl::event_stream _titleChanges; - rpl::event_stream _descriptionChanges; - rpl::event_stream _pageDataChanges; - - base::Timer _timer; - - rpl::lifetime _lifetime; - -}; - -WebpageProcessor::WebpageProcessor( - not_null history, - not_null field) -: _history(history) -, _api(&history->session().mtp()) -, _fieldLinksParser(field) -, _timer([=] { - if (!ShowWebPagePreview(_previewData) - || _previewLinks.isEmpty()) { - return; - } - getWebPagePreview(); -}) { - - _history->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return _previewData - && (_previewData->document || _previewData->photo); - }) | rpl::start_with_next([=] { - _paintRequests.fire({}); - }, _lifetime); - - _history->owner().webPageUpdates( - ) | rpl::filter([=](not_null page) { - return (_previewData == page.get()); - }) | rpl::start_with_next([=] { - updatePreview(); - }, _lifetime); - - _fieldLinksParser.list().changes( - ) | rpl::start_with_next([=](QStringList &&parsed) { - _parsedLinks = std::move(parsed); - checkPreview(); - }, _lifetime); -} - -rpl::producer<> WebpageProcessor::paintRequests() const { - return _paintRequests.events(); -} - -Data::WebPageDraft WebpageProcessor::draft() const { - return _previewDraft; -} - -void WebpageProcessor::setAllowed(bool allowed) { - _previewDraft.removed = !allowed; -} - -void WebpageProcessor::refreshDraft( - Data::WebPageDraft draft, - bool disable) { - // Save links from _field to _parsedLinks without generating preview. - _previewDraft = { .removed = true }; - _fieldLinksParser.setDisabled(disable); - _fieldLinksParser.parseNow(); - _parsedLinks = _fieldLinksParser.list().current(); - _previewDraft = draft; - checkPreview(); -} - -void WebpageProcessor::cancel() { - _api.request(base::take(_previewRequest)).cancel(); - _previewData = nullptr; - _previewLinks.clear(); - updatePreview(); -} - -void WebpageProcessor::updatePreview() { - _timer.cancel(); - auto t = QString(); - auto d = QString(); - if (ShowWebPagePreview(_previewData)) { - if (const auto till = _previewData->pendingTill) { - t = tr::lng_preview_loading(tr::now); - d = QStringView(_previewLinks).split(' ').at(0).toString(); - - const auto timeout = till - base::unixtime::now(); - _timer.callOnce( - std::max(timeout, 0) * crl::time(1000)); - } else { - const auto preview = ProcessWebPageData(_previewData); - t = preview.title; - d = preview.description; - } - } - _titleChanges.fire_copy(t); - _descriptionChanges.fire_copy(d); - _pageDataChanges.fire_copy(_previewData); - _paintRequests.fire({}); -} - -void WebpageProcessor::getWebPagePreview() { - const auto links = _previewLinks; - _previewRequest = _api.request( - MTPmessages_GetWebPagePreview( - MTP_flags(0), - MTP_string(links), - MTPVector() - )).done([=](const MTPMessageMedia &result) { - _previewRequest = 0; - result.match([=](const MTPDmessageMediaWebPage &d) { - const auto page = _history->owner().processWebpage(d.vwebpage()); - _previewCache.insert({ links, page->id }); - if (page->pendingTill > 0 - && page->pendingTill <= base::unixtime::now()) { - page->pendingTill = 0; - page->failed = true; - } - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = (page->id && !page->failed) - ? page.get() - : nullptr; - updatePreview(); - } - }, [=](const MTPDmessageMediaEmpty &d) { - _previewCache.insert({ links, 0 }); - if (links == _previewLinks && !_previewDraft.removed) { - _previewData = nullptr; - updatePreview(); - } - }, [](const auto &d) { - }); - }).fail([=] { - _previewRequest = 0; - }).send(); -} - -void WebpageProcessor::checkPreview() { - const auto previewRestricted = _history->peer - && _history->peer->amRestricted(ChatRestriction::EmbedLinks); - if (_previewDraft.removed || previewRestricted) { - cancel(); - return; - } - const auto newLinks = _parsedLinks.join(' '); - if (_previewLinks == newLinks) { - return; - } - _api.request(base::take(_previewRequest)).cancel(); - _previewLinks = newLinks; - if (_previewLinks.isEmpty()) { - if (ShowWebPagePreview(_previewData)) { - cancel(); - } - } 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)) { - cancel(); - } - } -} - -rpl::producer WebpageProcessor::titleChanges() const { - return _titleChanges.events(); -} - -rpl::producer WebpageProcessor::descriptionChanges() const { - return _descriptionChanges.events(); -} - -rpl::producer WebpageProcessor::pageDataChanges() const { - return _pageDataChanges.events(); -} - class FieldHeader final : public Ui::RpWidget { public: FieldHeader( @@ -350,10 +135,7 @@ public: void updateForwarding( Data::Thread *thread, Data::ResolvedForwardDraft items); - void previewRequested( - rpl::producer title, - rpl::producer description, - rpl::producer page); + void previewReady(rpl::producer parsed); void previewUnregister(); [[nodiscard]] bool isDisplayed() const; @@ -366,7 +148,6 @@ public: [[nodiscard]] rpl::producer scrollToItemRequests() const; [[nodiscard]] rpl::producer<> editPhotoRequests() const; [[nodiscard]] MessageToEdit queryToEdit(); - [[nodiscard]] Data::WebPageDraft webPageDraft() const; [[nodiscard]] FullReplyTo getDraftReply() const; [[nodiscard]] rpl::producer<> editCancelled() const { @@ -399,18 +180,14 @@ private: bool hasPreview() const; struct Preview { - WebPageData *data = nullptr; - Data::WebPageDraft draft; + Controls::WebpageParsed parsed; Ui::Text::String title; Ui::Text::String description; - bool cancelled = false; }; const std::shared_ptr _show; History *_history = nullptr; MsgId _topicRootId = 0; - rpl::variable _title; - rpl::variable _description; Preview _preview; rpl::event_stream<> _editCancelled; @@ -493,7 +270,7 @@ void FieldHeader::init() { st::historyReplyIcon.paint(p, position, width()); } - (ShowWebPagePreview(_preview.data) && !*leftIconPressed) + (_preview.parsed && !*leftIconPressed) ? paintWebPage( p, _history ? _history->peer : _data->session().user()) @@ -548,22 +325,6 @@ void FieldHeader::init() { update(); }); - _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, - d, - Ui::DialogTextOptions()); - }, lifetime()); - setMouseTracking(true); events( ) | rpl::filter([=](not_null event) { @@ -619,28 +380,32 @@ void FieldHeader::init() { if (!isEditingMessage() && readyToForward()) { _forwardPanel->editOptions(_show); } else if (!isEditingMessage() && reply) { - using namespace Controls; - const auto highlight = [=] { - _scrollToItemRequests.fire_copy(reply.messageId); - }; - const auto history = _history; - const auto topicRootId = _topicRootId; - const auto done = [=](FullReplyTo replyTo) { - if (replyTo) { - replyToMessage(replyTo); - } else { - _replyCancelled.fire({}); - } - }; - const auto clearOldReplyTo = [=, id = reply.messageId] { - ClearDraftReplyTo(history, topicRootId, id); - }; - EditReplyOptions( - _show, - reply, - done, - highlight, - clearOldReplyTo); + //using namespace Controls; + //const auto highlight = [=] { + // _scrollToItemRequests.fire_copy(reply.messageId); + //}; + //const auto history = _history; + //const auto topicRootId = _topicRootId; + //const auto done = [=]( + // FullReplyTo replyTo, + // Data::WebPageDraft webpage) { + // if (replyTo) { + // replyToMessage(replyTo); + // } else { + // _replyCancelled.fire({}); + // } + + //}; + //const auto clearOldReplyTo = [=, id = reply.messageId] { + // ClearDraftReplyTo(history, topicRootId, id); + //}; + //EditDraftOptions( + // _show, + // _history, + // Data::Draft( reply, + // done, + // highlight, + // clearOldReplyTo); } else { auto id = isEditingMessage() ? _editMsgId.current() @@ -683,17 +448,17 @@ void FieldHeader::setShownMessage(HistoryItem *item) { _shownMessage = item; if (item) { updateShownMessageText(); - if (item->fullId() == _editMsgId.current()) { - _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; - } - } - } + //if (item->fullId() == _editMsgId.current()) { + // _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; + // } + // } + //} } else { _shownMessageText.clear(); resolveMessageData(); @@ -737,34 +502,22 @@ void FieldHeader::resolveMessageData() { _data->session().api().requestMessageData(peer, itemId, callback); } -void FieldHeader::previewRequested( - rpl::producer title, - rpl::producer description, - rpl::producer page) { +void FieldHeader::previewReady( + rpl::producer parsed) { _previewLifetime.destroy(); std::move( - title - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](const QString &t) { - _title = t; - }, _previewLifetime); - - std::move( - description - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](const QString &d) { - _description = d; - }, _previewLifetime); - - std::move( - page - ) | rpl::filter([=] { - return !_preview.cancelled; - }) | rpl::start_with_next([=](WebPageData *p) { - _preview.data = p; + parsed + ) | rpl::start_with_next([=](Controls::WebpageParsed parsed) { + _preview.parsed = std::move(parsed); + _preview.title.setText( + st::msgNameStyle, + _preview.parsed.title, + Ui::NameTextOptions()); + _preview.description.setText( + st::messageTextStyle, + _preview.parsed.description, + Ui::DialogTextOptions()); updateVisible(); }, _previewLifetime); } @@ -774,7 +527,7 @@ void FieldHeader::previewUnregister() { } void FieldHeader::paintWebPage(Painter &p, not_null context) { - Expects(ShowWebPagePreview(_preview.data)); + Expects(!!_preview.parsed); const auto textTop = st::msgReplyPadding.top(); auto previewLeft = st::historyReplySkip + st::msgReplyBarSkip; @@ -784,7 +537,7 @@ void FieldHeader::paintWebPage(Painter &p, not_null context) { (st::historyReplyHeight - st::historyReplyPreview) / 2, st::historyReplyPreview, st::historyReplyPreview); - if (HistoryView::DrawWebPageDataPreview(p, _preview.data, context, to)) { + if (_preview.parsed.drawPreview(p, to)) { previewLeft += st::historyReplyPreview + st::msgReplyBarSkip; } const auto elidedWidth = width() @@ -965,13 +718,7 @@ FullReplyTo FieldHeader::replyingToMessage() const { } bool FieldHeader::hasPreview() const { - return ShowWebPagePreview(_preview.data); -} - -Data::WebPageDraft FieldHeader::webPageDraft() const { - return hasPreview() - ? Data::WebPageDraft{ .id = _preview.data->id } - : Data::WebPageDraft{ .removed = true }; + return !!_preview.parsed; } FullReplyTo FieldHeader::getDraftReply() const { @@ -1476,8 +1223,7 @@ void ComposeControls::setFieldText( | TextUpdateEvent::SendTyping; if (_preview) { - _preview->cancel(); - _preview->setAllowed(true); + _preview->checkNow(false); } } @@ -1626,7 +1372,7 @@ void ComposeControls::init() { _header->previewCancelled( ) | rpl::start_with_next([=] { if (_preview) { - _preview->setAllowed(false); + _preview->apply({ .removed = true }); } _saveDraftText = true; _saveDraftStart = crl::now(); @@ -2010,7 +1756,7 @@ void ComposeControls::fieldChanged() { updateSendButtonType(); _hasSendText = HasSendText(_field); if (!_hasSendText.current() && _preview) { - _preview->setAllowed(true); + _preview->apply({}); } if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); @@ -2180,7 +1926,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { } _header->editMessage({}); _header->replyToMessage({}); - _preview->refreshDraft({}, false); + if (_preview) { + _preview->apply({}); + } _canReplaceMedia = false; _photoEditMedia = nullptr; return; @@ -2194,15 +1942,13 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { draft->cursor.applyTo(_field); _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; if (_preview) { - const auto disablePreview = (editDraft != nullptr); - _preview->refreshDraft(draft->webpage, disablePreview); + _preview->apply(draft->webpage, draft != editDraft); } if (draft == editDraft) { const auto resolve = [=] { if (const auto item = _history->owner().message(editingId)) { const auto media = item->media(); - const auto disablePreview = media && !media->webpage(); _canReplaceMedia = media && media->allowsEditMedia(); _photoEditMedia = (_canReplaceMedia && _regularWindow @@ -2216,7 +1962,11 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { item->fullId()); } _header->editMessage(editingId, _photoEditMedia != nullptr); - _preview->refreshDraft(_preview->draft(), disablePreview); + if (_preview) { + _preview->apply( + Data::WebPageDraft::FromItem(item), + false); + } return true; } _canReplaceMedia = false; @@ -3070,9 +2820,11 @@ void ComposeControls::initWebpageProcess() { return; } - _preview = std::make_unique(_history, _field); + _preview = std::make_unique( + _history, + _field); - _preview->paintRequests( + _preview->repaintRequests( ) | rpl::start_with_next(crl::guard(_header.get(), [=] { _header->update(); }), _historyLifetime); @@ -3088,7 +2840,7 @@ void ComposeControls::initWebpageProcess() { return update.flags; }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) { if (flags & Data::PeerUpdate::Flag::Rights) { - _preview->checkPreview(); + _preview->checkNow(false); updateStickersByEmoji(); updateFieldPlaceholder(); } @@ -3106,10 +2858,7 @@ void ComposeControls::initWebpageProcess() { } }, _historyLifetime); - _header->previewRequested( - _preview->titleChanges(), - _preview->descriptionChanges(), - _preview->pageDataChanges()); + _header->previewReady(_preview->parsedValue()); } void ComposeControls::initForwardProcess() { @@ -3129,7 +2878,7 @@ void ComposeControls::initForwardProcess() { } Data::WebPageDraft ComposeControls::webPageDraft() const { - return _header->webPageDraft(); + return _preview ? _preview->draft() : Data::WebPageDraft(); } rpl::producer ComposeControls::scrollRequests() const { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 06d66e69d..d6a132af2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -79,15 +79,15 @@ namespace Api { enum class SendProgressType; } // namespace Api -namespace HistoryView { - -namespace Controls { +namespace HistoryView::Controls { class VoiceRecordBar; class TTLButton; -} // namespace Controls +class WebpageProcessor; +} // namespace HistoryView::Controls + +namespace HistoryView { class FieldHeader; -class WebpageProcessor; enum class ComposeControlsMode { Normal, @@ -422,7 +422,7 @@ private: std::shared_ptr _photoEditMedia; bool _canReplaceMedia = false; - std::unique_ptr _preview; + std::unique_ptr _preview; Fn _raiseEmojiSuggestions; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp new file mode 100644 index 000000000..1147395aa --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -0,0 +1,846 @@ +/* +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/controls/history_view_draft_options.h" + +#include "base/unixtime.h" +#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_changes.h" +#include "data/data_drafts.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/data_thread.h" +#include "data/data_user.h" +#include "data/data_web_page.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/effects/path_shift_gradient.h" +#include "ui/layers/generic_box.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/painter.h" +#include "window/themes/window_theme.h" +#include "window/section_widget.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +#include +#include + +namespace HistoryView::Controls { +namespace { + +enum class Section { + Reply, + Link, +}; + +class PreviewDelegate final : public DefaultElementDelegate { +public: + PreviewDelegate( + not_null parent, + not_null st, + Fn update); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; + Context elementContext() override; + +private: + const not_null _parent; + const std::unique_ptr _pathGradient; + +}; + +[[nodiscard]] std::unique_ptr DefaultThemeOn( + rpl::lifetime &lifetime) { + auto result = std::make_unique(); + + using namespace Window::Theme; + const auto push = [=, raw = result.get()] { + const auto background = Background(); + const auto &paper = background->paper(); + raw->setBackground({ + .prepared = background->prepared(), + .preparedForTiled = background->preparedForTiled(), + .gradientForFill = background->gradientForFill(), + .colorForFill = background->colorForFill(), + .colors = paper.backgroundColors(), + .patternOpacity = paper.patternOpacity(), + .gradientRotation = paper.gradientRotation(), + .isPattern = paper.isPattern(), + .tile = background->tile(), + }); + }; + + push(); + Background()->updates( + ) | rpl::start_with_next([=](const BackgroundUpdate &update) { + if (update.type == BackgroundUpdate::Type::New + || update.type == BackgroundUpdate::Type::Changed) { + push(); + } + }, lifetime); + + return result; +} + +class PreviewWrap final : public Ui::RpWidget { +public: + PreviewWrap( + not_null box, + not_null history); + ~PreviewWrap(); + + [[nodiscard]] rpl::producer showQuoteSelector( + not_null item, + const TextWithEntities "e); + [[nodiscard]] rpl::producer showLinkSelector( + const TextWithTags &message, + Data::WebPageDraft webpage); + +private: + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + + void initElement(); + void startSelection(TextSelectType type); + [[nodiscard]] TextSelection resolveNewSelection() const; + + const not_null _box; + const not_null _history; + const std::unique_ptr _theme; + const std::unique_ptr _style; + const std::unique_ptr _delegate; + + Section _section = Section::Reply; + HistoryItem *_draftItem = nullptr; + std::unique_ptr _element; + rpl::variable _selection; + Ui::PeerUserpicView _userpic; + rpl::lifetime _elementLifetime; + + QPoint _position; + + base::Timer _trippleClickTimer; + TextSelectType _selectType = TextSelectType::Letters; + uint16 _symbol = 0; + uint16 _selectionStartSymbol = 0; + bool _onlyMessageText = false; + bool _afterSymbol = false; + bool _selectionStartAfterSymbol = false; + bool _over = false; + bool _textCursor = false; + bool _linkCursor = false; + bool _selecting = false; + +}; + +PreviewWrap::PreviewWrap( + not_null box, + not_null history) +: RpWidget(box) +, _box(box) +, _history(history) +, _theme(DefaultThemeOn(lifetime())) +, _style(std::make_unique()) +, _delegate(std::make_unique( + box, + _style.get(), + [=] { update(); })) +, _position(0, st::msgMargin.bottom()) { + _style->apply(_theme.get()); + + const auto session = &_history->session(); + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null view) { + if (view == _element.get()) { + update(); + } + }, lifetime()); + + _selection.changes() | rpl::start_with_next([=] { + update(); + }, lifetime()); + + _box->setAttribute(Qt::WA_OpaquePaintEvent, false); + + _box->paintRequest() | rpl::start_with_next([=](QRect clip) { + const auto geometry = Ui::MapFrom(_box, this, rect()); + const auto fill = geometry.intersected(clip); + if (!fill.isEmpty()) { + auto p = QPainter(_box); + p.setClipRect(fill); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(_box->width(), _box->window()->height()), + fill); + } + }, lifetime()); + + setMouseTracking(true); +} + +PreviewWrap::~PreviewWrap() { + _selection.reset(TextSelection()); + _elementLifetime.destroy(); + _element = nullptr; + if (_draftItem) { + _draftItem->destroy(); + } +} + +rpl::producer PreviewWrap::showQuoteSelector( + not_null item, + const TextWithEntities "e) { + auto element = item->createView(_delegate.get()); + _selection.reset(element->selectionFromQuote(quote)); + _element = std::move(element); + + if (const auto was = base::take(_draftItem)) { + was->destroy(); + } + + const auto media = item->media(); + _onlyMessageText = media + && (media->webpage() + || media->game() + || (!media->photo() && !media->document())); + _section = Section::Reply; + + initElement(); + + return _selection.value( + ) | rpl::map([=](TextSelection selection) { + return _element->selectedQuote(selection); + }); +} + +rpl::producer PreviewWrap::showLinkSelector( + const TextWithTags &message, + Data::WebPageDraft webpage) { + _selection.reset(TextSelection()); + + _element = nullptr; + if (const auto was = base::take(_draftItem)) { + was->destroy(); + } + using Flag = MTPDmessageMediaWebPage::Flag; + _draftItem = _history->addNewLocalMessage( + _history->nextNonHistoryEntryId(), + (MessageFlag::FakeHistoryItem + | MessageFlag::Outgoing + | (webpage.invert ? MessageFlag::InvertMedia : MessageFlag())), + UserId(), // via + FullReplyTo(), + base::unixtime::now(), // date + _history->session().userPeerId(), + QString(), // postAuthor + TextWithEntities{ + message.text, + TextUtilities::ConvertTextTagsToEntities(message.tags), + }, + MTP_messageMediaWebPage( + MTP_flags(Flag() + | (webpage.forceLargeMedia + ? Flag::f_force_large_media + : Flag()) + | (webpage.forceSmallMedia + ? Flag::f_force_small_media + : Flag())), + MTP_webPagePending( + MTP_flags(webpage.url.isEmpty() + ? MTPDwebPagePending::Flag() + : MTPDwebPagePending::Flag::f_url), + MTP_long(webpage.id), + MTP_string(webpage.url), + MTP_int(0))), + HistoryMessageMarkupData(), + uint64(0)); // groupedId + _element = _draftItem->createView(_delegate.get()); + _selectType = TextSelectType::Letters; + _symbol = _selectionStartSymbol = 0; + _afterSymbol = _selectionStartAfterSymbol = false; + _section = Section::Link; + + initElement(); + + return rpl::never(); +} + +void PreviewWrap::paintEvent(QPaintEvent *e) { + if (!_element) { + return; + } + + auto p = Painter(this); + auto hq = PainterHighQualityEnabler(p); + + auto context = _theme->preparePaintContext( + _style.get(), + rect(), + e->rect(), + !window()->isActiveWindow()); + context.outbg = _element->hasOutLayout(); + context.selection = _selecting + ? resolveNewSelection() + : _selection.current(); + + p.translate(_position); + _element->draw(p, context); + + if (_element->displayFromPhoto()) { + auto userpicMinBottomSkip = st::historyPaddingBottom + + st::msgMargin.bottom(); + auto userpicBottom = height() + - _element->marginBottom() + - _element->marginTop(); + const auto item = _element->data(); + const auto userpicTop = userpicBottom - st::msgPhotoSize; + if (const auto from = item->displayFrom()) { + from->paintUserpicLeft( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } else if (const auto info = item->hiddenSenderInfo()) { + if (info->customUserpic.empty()) { + info->emptyUserpic.paintCircle( + p, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + } else { + const auto valid = info->paintCustomUserpic( + p, + _userpic, + st::historyPhotoLeft, + userpicTop, + width(), + st::msgPhotoSize); + if (!valid) { + info->customUserpic.load( + &item->history()->session(), + item->fullId()); + } + } + } else { + Unexpected("Corrupt forwarded information in message."); + } + } +} + +void PreviewWrap::leaveEventHook(QEvent *e) { + if (!_element || !_over) { + return; + } + _over = false; + _textCursor = false; + _linkCursor = false; + if (!_selecting) { + setCursor(style::cur_default); + } +} + +void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { + if (!_element) { + return; + } + using Flag = Ui::Text::StateRequest::Flag; + auto request = StateRequest{ + .flags = (_section == Section::Reply + ? Flag::LookupSymbol + : Flag::LookupLink), + .onlyMessageText = (_section == Section::Link || _onlyMessageText), + }; + auto resolved = _element->textState( + e->pos() - _position, + request); + _over = true; + const auto text = (_section == Section::Reply) + && (resolved.cursor == CursorState::Text); + const auto link = (_section == Section::Link) && resolved.link; + if (_textCursor != text || _linkCursor != link) { + _textCursor = text; + _linkCursor = link; + setCursor((text || _selecting) + ? style::cur_text + : link + ? style::cur_pointer + : style::cur_default); + } + if (_symbol != resolved.symbol + || _afterSymbol != resolved.afterSymbol) { + _symbol = resolved.symbol; + _afterSymbol = resolved.afterSymbol; + if (_selecting) { + update(); + } + } +} + +void PreviewWrap::mousePressEvent(QMouseEvent *e) { + if (!_over) { + return; + } else if (_section == Section::Reply) { + startSelection(_trippleClickTimer.isActive() + ? TextSelectType::Paragraphs + : TextSelectType::Letters); + } +} + +void PreviewWrap::mouseReleaseEvent(QMouseEvent *e) { + if (!_selecting) { + return; + } else if (_section == Section::Reply) { + const auto result = resolveNewSelection(); + _selecting = false; + _selectType = TextSelectType::Letters; + if (!_textCursor) { + setCursor(style::cur_default); + } + _selection = result; + } +} + +void PreviewWrap::mouseDoubleClickEvent(QMouseEvent *e) { + if (!_over) { + return; + } else if (_section == Section::Reply) { + startSelection(TextSelectType::Words); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } +} + +void PreviewWrap::initElement() { + _elementLifetime.destroy(); + + if (!_element) { + return; + } + _element->initDimensions(); + + widthValue( + ) | rpl::filter([=](int width) { + return width > st::msgMinWidth; + }) | rpl::start_with_next([=](int width) { + const auto height = _position.y() + + _element->resizeGetHeight(width) + + st::msgMargin.top(); + resize(width, height); + }, _elementLifetime); +} + +TextSelection PreviewWrap::resolveNewSelection() const { + if (_section != Section::Reply) { + return TextSelection(); + } + const auto make = [](uint16 symbol, bool afterSymbol) { + return uint16(symbol + (afterSymbol ? 1 : 0)); + }; + const auto first = make(_symbol, _afterSymbol); + const auto second = make( + _selectionStartSymbol, + _selectionStartAfterSymbol); + const auto result = (first <= second) + ? TextSelection{ first, second } + : TextSelection{ second, first }; + return _element->adjustSelection(result, _selectType); +} + +void PreviewWrap::startSelection(TextSelectType type) { + if (_selecting && _selectType >= type) { + return; + } + _selecting = true; + _selectType = type; + _selectionStartSymbol = _symbol; + _selectionStartAfterSymbol = _afterSymbol; + if (!_textCursor) { + setCursor(style::cur_text); + } + update(); +} + +PreviewDelegate::PreviewDelegate( + not_null parent, + not_null st, + Fn update) +: _parent(parent) +, _pathGradient(MakePathShiftGradient(st, update)) { +} + +bool PreviewDelegate::elementAnimationsPaused() { + return _parent->window()->isActiveWindow(); +} + +auto PreviewDelegate::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + +Context PreviewDelegate::elementContext() { + return Context::History; +} + +} // namespace + +void ShowReplyToChatBox( + std::shared_ptr show, + FullReplyTo reply, + Fn clearOldDraft) { + class Controller final : public ChooseRecipientBoxController { + public: + using Chosen = not_null; + + Controller(not_null session) + : ChooseRecipientBoxController( + session, + [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, + nullptr) { + } + + void rowClicked(not_null row) override final { + ChooseRecipientBoxController::rowClicked(row); + } + + [[nodiscard]] rpl::producer singleChosen() const{ + return _singleChosen.events(); + } + + bool respectSavedMessagesChat() const override { + return false; + } + + private: + void prepareViewHook() override { + delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); + } + + rpl::event_stream _singleChosen; + + }; + + struct State { + not_null box; + not_null controller; + base::unique_qptr menu; + }; + const auto session = &show->session(); + const auto state = [&] { + auto controller = std::make_unique(session); + const auto controllerRaw = controller.get(); + auto box = Box(std::move(controller), [=]( + not_null box) { + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }); + const auto boxRaw = box.data(); + show->show(std::move(box)); + auto state = State{ boxRaw, controllerRaw }; + return boxRaw->lifetime().make_state(std::move(state)); + }(); + + auto chosen = [=](not_null thread) mutable { + const auto history = thread->owningHistory(); + const auto topicRootId = thread->topicRootId(); + const auto draft = history->localDraft(topicRootId); + const auto textWithTags = draft + ? draft->textWithTags + : TextWithTags(); + const auto cursor = draft ? draft->cursor : MessageCursor(); + reply.topicRootId = topicRootId; + history->setLocalDraft(std::make_unique( + textWithTags, + reply, + cursor, + Data::WebPageDraft())); + history->clearLocalEditDraft(topicRootId); + history->session().changes().entryUpdated( + thread, + Data::EntryUpdate::Flag::LocalDraftSet); + + if (clearOldDraft) { + crl::on_main(&history->session(), clearOldDraft); + } + return true; + }; + auto callback = [=, chosen = std::move(chosen)]( + Controller::Chosen thread) mutable { + const auto weak = Ui::MakeWeak(state->box); + if (!chosen(thread)) { + return; + } else if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + state->controller->singleChosen( + ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); +} + +void EditDraftOptions( + std::shared_ptr show, + not_null history, + Data::Draft draft, + Fn done, + Fn highlight, + Fn clearOldDraft) { + const auto session = &show->session(); + const auto replyItem = session->data().message(draft.reply.messageId); + const auto previewDataRaw = draft.webpage.id + ? session->data().webpage(draft.webpage.id).get() + : nullptr; + const auto previewData = (previewDataRaw + && !previewDataRaw->pendingTill + && !previewDataRaw->failed) + ? previewDataRaw + : nullptr; + if (!replyItem && !previewData) { + return; + } + show->show(Box([=](not_null box) { + box->setWidth(st::boxWideWidth); + + struct State { + rpl::variable
shown; + rpl::lifetime shownLifetime; + rpl::variable quote; + Data::WebPageDraft webpage; + Ui::SettingsSlider *tabs = nullptr; + PreviewWrap *wrap = nullptr; + }; + const auto state = box->lifetime().make_state(); + state->quote = draft.reply.quote; + state->webpage = draft.webpage; + state->shown = previewData ? Section::Link : Section::Reply; + if (replyItem && previewData) { + box->setNoContentMargin(true); + state->tabs = box->setPinnedToTopContent( + object_ptr( + box.get(), + st::defaultTabsSlider)); + state->tabs->resizeToWidth(st::boxWideWidth); + state->tabs->move(0, 0); + state->tabs->setRippleTopRoundRadius(st::boxRadius); + state->tabs->setSections({ + tr::lng_reply_header_short(tr::now), + tr::lng_link_header_short(tr::now), + }); + state->tabs->setActiveSectionFast(1); + state->tabs->sectionActivated( + ) | rpl::start_with_next([=](int section) { + state->shown = section ? Section::Link : Section::Reply; + }, box->lifetime()); + } else { + box->setTitle(previewData + ? tr::lng_link_options_header() + : draft.reply.quote.empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + } + + const auto bottom = box->setPinnedToBottomContent( + object_ptr(box)); + const auto addSkip = [=] { + const auto skip = bottom->add(object_ptr( + bottom, + st::settingsPrivacySkipTop)); + skip->paintRequest() | rpl::start_with_next([=](QRect clip) { + QPainter(skip).fillRect(clip, st::boxBg); + }, skip->lifetime()); + }; + + const auto resolveReply = [=] { + auto result = draft.reply; + result.quote = state->quote.current(); + return result; + }; + const auto finish = [=]( + FullReplyTo result, + Data::WebPageDraft webpage) { + const auto weak = Ui::MakeWeak(box); + done(std::move(result), std::move(webpage)); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + const auto setupReplyActions = [=] { + addSkip(); + + Settings::AddButton( + bottom, + tr::lng_reply_in_another_chat(), + st::settingsButton, + { &st::menuIconReplace } + )->setClickedCallback([=] { + ShowReplyToChatBox(show, resolveReply(), clearOldDraft); + }); + + Settings::AddButton( + bottom, + tr::lng_reply_show_in_chat(), + st::settingsButton, + { &st::menuIconShowInChat } + )->setClickedCallback(highlight); + + Settings::AddButton( + bottom, + tr::lng_reply_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish({}, state->webpage); + }); + + if (!replyItem->originalText().empty()) { + addSkip(); + Settings::AddDividerText( + bottom, + tr::lng_reply_about_quote()); + } + }; + const auto setupLinkActions = [=] { + addSkip(); + + if (!draft.textWithTags.empty()) { + Settings::AddButton( + bottom, + (state->webpage.invert + ? tr::lng_link_move_down() + : tr::lng_link_move_up()), + st::settingsButton, + { state->webpage.invert + ? &st::menuIconBelow + : &st::menuIconAbove } + )->setClickedCallback([=] { + state->webpage.invert = !state->webpage.invert; + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + if (previewData->hasLargeMedia) { + const auto small = state->webpage.forceSmallMedia + || (!state->webpage.forceLargeMedia + && previewData->computeDefaultSmallMedia()); + Settings::AddButton( + bottom, + (small + ? tr::lng_link_enlarge_photo() + : tr::lng_link_shrink_photo()), + st::settingsButton, + { small ? &st::menuIconEnlarge : &st::menuIconShrink } + )->setClickedCallback([=] { + if (small) { + state->webpage.forceSmallMedia = false; + state->webpage.forceLargeMedia = true; + } else { + state->webpage.forceLargeMedia = false; + state->webpage.forceSmallMedia = true; + } + state->webpage.manual = true; + state->shown.force_assign(Section::Link); + }); + } + + Settings::AddButton( + bottom, + tr::lng_link_remove(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention } + )->setClickedCallback([=] { + finish(resolveReply(), { .removed = true }); + }); + + if (true) { + addSkip(); + Settings::AddDividerText( + bottom, + tr::lng_link_about_choose()); + } + }; + + state->wrap = box->addRow( + object_ptr(box, history), + {}); + state->shown.value() | rpl::start_with_next([=](Section shown) { + bottom->clear(); + state->shownLifetime.destroy(); + if (shown == Section::Reply) { + state->quote = state->wrap->showQuoteSelector( + replyItem, + state->quote.current()); + setupReplyActions(); + } else { + state->wrap->showLinkSelector( + draft.textWithTags, + state->webpage + ) | rpl::start_with_next([=](QString url) { + }, state->shownLifetime); + setupLinkActions(); + } + }, box->lifetime()); + + auto save = rpl::combine( + state->quote.value(), + state->shown.value() + ) | rpl::map([=](const TextWithEntities "e, Section shown) { + return (quote.empty() || shown != Section::Reply) + ? tr::lng_settings_save() + : tr::lng_reply_quote_selected(); + }) | rpl::flatten_latest(); + box->addButton(std::move(save), [=] { + finish(resolveReply(), state->webpage); + }); + + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); + + if (replyItem) { + session->data().itemRemoved( + ) | rpl::filter([=](not_null removed) { + return removed == replyItem; + }) | rpl::start_with_next([=] { + if (previewData) { + state->tabs = nullptr; + box->setPinnedToTopContent( + object_ptr(nullptr)); + box->setNoContentMargin(false); + box->setTitle(state->quote.current().empty() + ? tr::lng_reply_options_header() + : tr::lng_reply_options_quote()); + state->shown = Section::Link; + } else { + box->closeBox(); + } + }, box->lifetime()); + } + })); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h similarity index 80% rename from Telegram/SourceFiles/history/view/controls/history_view_reply_options.h rename to Telegram/SourceFiles/history/view/controls/history_view_draft_options.h index 048fd8eba..6e3def8cc 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h @@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_drafts.h" + +class History; + namespace ChatHelpers { class Show; } // namespace ChatHelpers @@ -17,10 +21,11 @@ class SessionController; namespace HistoryView::Controls { -void EditReplyOptions( +void EditDraftOptions( std::shared_ptr show, - FullReplyTo reply, - Fn done, + not_null history, + Data::Draft draft, + Fn done, Fn highlight, Fn clearOldDraft); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp deleted file mode 100644 index 0eb6f8aeb..000000000 --- a/Telegram/SourceFiles/history/view/controls/history_view_reply_options.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/* -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/controls/history_view_reply_options.h" - -#include "boxes/peer_list_box.h" -#include "boxes/peer_list_controllers.h" -#include "chat_helpers/compose/compose_show.h" -#include "data/data_changes.h" -#include "data/data_drafts.h" -#include "data/data_file_origin.h" -#include "data/data_session.h" -#include "data/data_thread.h" -#include "history/history.h" -#include "history/history_item.h" -#include "history/history_item_components.h" -#include "history/view/history_view_element.h" -#include "history/view/history_view_cursor_state.h" -#include "lang/lang_keys.h" -#include "main/main_session.h" -#include "settings/settings_common.h" -#include "ui/chat/chat_style.h" -#include "ui/chat/chat_theme.h" -#include "ui/effects/path_shift_gradient.h" -#include "ui/layers/generic_box.h" -#include "ui/widgets/buttons.h" -#include "ui/painter.h" -#include "window/themes/window_theme.h" -#include "window/section_widget.h" -#include "window/window_session_controller.h" -#include "styles/style_chat.h" -#include "styles/style_layers.h" -#include "styles/style_menu_icons.h" -#include "styles/style_settings.h" - -#include -#include - -namespace HistoryView::Controls { -namespace { - -class PreviewDelegate final : public DefaultElementDelegate { -public: - PreviewDelegate( - not_null parent, - not_null st, - Fn update); - - bool elementAnimationsPaused() override; - not_null elementPathShiftGradient() override; - Context elementContext() override; - -private: - const not_null _parent; - const std::unique_ptr _pathGradient; - -}; - -[[nodiscard]] std::unique_ptr DefaultThemeOn( - rpl::lifetime &lifetime) { - auto result = std::make_unique(); - - using namespace Window::Theme; - const auto push = [=, raw = result.get()] { - const auto background = Background(); - const auto &paper = background->paper(); - raw->setBackground({ - .prepared = background->prepared(), - .preparedForTiled = background->preparedForTiled(), - .gradientForFill = background->gradientForFill(), - .colorForFill = background->colorForFill(), - .colors = paper.backgroundColors(), - .patternOpacity = paper.patternOpacity(), - .gradientRotation = paper.gradientRotation(), - .isPattern = paper.isPattern(), - .tile = background->tile(), - }); - }; - - push(); - Background()->updates( - ) | rpl::start_with_next([=](const BackgroundUpdate &update) { - if (update.type == BackgroundUpdate::Type::New - || update.type == BackgroundUpdate::Type::Changed) { - push(); - } - }, lifetime); - - return result; -} - -[[nodiscard]] rpl::producer AddQuoteTracker( - not_null box, - std::shared_ptr show, - not_null item, - const TextWithEntities "e) { - struct State { - std::unique_ptr theme; - std::unique_ptr style; - std::unique_ptr delegate; - std::unique_ptr element; - rpl::variable selection; - Ui::PeerUserpicView userpic; - QPoint position; - - base::Timer trippleClickTimer; - TextSelectType selectType = TextSelectType::Letters; - uint16 symbol = 0; - bool afterSymbol = false; - bool textCursor = false; - bool selecting = false; - bool over = false; - uint16 selectionStartSymbol = 0; - bool selectionStartAfterSymbol = false; - }; - - const auto preview = box->addRow(object_ptr(box), {}); - const auto state = preview->lifetime().make_state(); - state->theme = DefaultThemeOn(preview->lifetime()); - - state->style = std::make_unique(); - state->style->apply(state->theme.get()); - - state->delegate = std::make_unique( - box, - state->style.get(), - [=] { preview->update(); }); - - state->element = item->createView(state->delegate.get()); - state->element->initDimensions(); - state->position = QPoint(0, st::msgMargin.bottom()); - - state->selection = state->element->selectionFromQuote(quote); - - const auto session = &show->session(); - session->data().viewRepaintRequest( - ) | rpl::start_with_next([=](not_null view) { - if (view == state->element.get()) { - preview->update(); - } - }, preview->lifetime()); - - state->selection.changes() | rpl::start_with_next([=] { - preview->update(); - }, preview->lifetime()); - - const auto resolveNewSelection = [=] { - const auto make = [](uint16 symbol, bool afterSymbol) { - return uint16(symbol + (afterSymbol ? 1 : 0)); - }; - const auto first = make(state->symbol, state->afterSymbol); - const auto second = make( - state->selectionStartSymbol, - state->selectionStartAfterSymbol); - const auto result = (first <= second) - ? TextSelection{ first, second } - : TextSelection{ second, first }; - return state->element->adjustSelection(result, state->selectType); - }; - const auto startSelection = [=](TextSelectType type) { - if (state->selecting && state->selectType >= type) { - return; - } - state->selecting = true; - state->selectType = type; - state->selectionStartSymbol = state->symbol; - state->selectionStartAfterSymbol = state->afterSymbol; - if (!state->textCursor) { - preview->setCursor(style::cur_text); - } - preview->update(); - }; - const auto media = item->media(); - const auto onlyMessageText = media - && (media->webpage() - || media->game() - || (!media->photo() && !media->document())); - preview->setMouseTracking(true); - preview->events() | rpl::start_with_next([=](not_null e) { - const auto type = e->type(); - const auto mouse = static_cast(e.get()); - if (type == QEvent::MouseMove) { - auto request = StateRequest{ - .flags = Ui::Text::StateRequest::Flag::LookupSymbol, - .onlyMessageText = onlyMessageText, - }; - auto resolved = state->element->textState( - mouse->pos() - state->position, - request); - state->over = true; - const auto text = (resolved.cursor == CursorState::Text); - if (state->textCursor != text) { - state->textCursor = text; - preview->setCursor((text || state->selecting) - ? style::cur_text - : style::cur_default); - } - if (state->symbol != resolved.symbol - || state->afterSymbol != resolved.afterSymbol) { - state->symbol = resolved.symbol; - state->afterSymbol = resolved.afterSymbol; - if (state->selecting) { - preview->update(); - } - } - } else if (type == QEvent::Leave && state->over) { - state->over = false; - if (state->textCursor) { - state->textCursor = false; - if (!state->selecting) { - preview->setCursor(style::cur_default); - } - } - } else if (type == QEvent::MouseButtonDblClick && state->over) { - startSelection(TextSelectType::Words); - state->trippleClickTimer.callOnce( - QApplication::doubleClickInterval()); - } else if (type == QEvent::MouseButtonPress && state->over) { - startSelection(state->trippleClickTimer.isActive() - ? TextSelectType::Paragraphs - : TextSelectType::Letters); - } else if (type == QEvent::MouseButtonRelease && state->selecting) { - const auto result = resolveNewSelection(); - state->selecting = false; - state->selectType = TextSelectType::Letters; - state->selection = result; - if (!state->textCursor) { - preview->setCursor(style::cur_default); - } - } - }, preview->lifetime()); - - preview->widthValue( - ) | rpl::filter([=](int width) { - return width > st::msgMinWidth; - }) | rpl::start_with_next([=](int width) { - const auto height = state->element->resizeGetHeight(width) - + state->position.y() - + st::msgMargin.top(); - preview->resize(width, height); - }, preview->lifetime()); - - box->setAttribute(Qt::WA_OpaquePaintEvent, false); - box->paintRequest() | rpl::start_with_next([=](QRect clip) { - Window::SectionWidget::PaintBackground( - state->theme.get(), - box, - box->window()->height(), - 0, - clip); - }, box->lifetime()); - - preview->paintRequest() | rpl::start_with_next([=](QRect clip) { - auto p = Painter(preview); - auto hq = PainterHighQualityEnabler(p); - p.translate(state->position); - auto context = state->theme->preparePaintContext( - state->style.get(), - preview->rect(), - clip, - !box->window()->isActiveWindow()); - context.outbg = state->element->hasOutLayout(); - context.selection = state->selecting - ? resolveNewSelection() - : state->selection.current(); - state->element->draw(p, context); - if (state->element->displayFromPhoto()) { - auto userpicMinBottomSkip = st::historyPaddingBottom - + st::msgMargin.bottom(); - auto userpicBottom = preview->height() - - state->element->marginBottom() - - state->element->marginTop(); - const auto userpicTop = userpicBottom - st::msgPhotoSize; - if (const auto from = item->displayFrom()) { - from->paintUserpicLeft( - p, - state->userpic, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - } else if (const auto info = item->hiddenSenderInfo()) { - if (info->customUserpic.empty()) { - info->emptyUserpic.paintCircle( - p, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - } else { - const auto valid = info->paintCustomUserpic( - p, - state->userpic, - st::historyPhotoLeft, - userpicTop, - preview->width(), - st::msgPhotoSize); - if (!valid) { - info->customUserpic.load(session, item->fullId()); - } - } - } else { - Unexpected("Corrupt forwarded information in message."); - } - } - }, preview->lifetime()); - - return state->selection.value( - ) | rpl::map([=](TextSelection selection) { - return state->element->selectedQuote(selection); - }); -} - -PreviewDelegate::PreviewDelegate( - not_null parent, - not_null st, - Fn update) -: _parent(parent) -, _pathGradient(MakePathShiftGradient(st, update)) { -} - -bool PreviewDelegate::elementAnimationsPaused() { - return _parent->window()->isActiveWindow(); -} - -auto PreviewDelegate::elementPathShiftGradient() --> not_null { - return _pathGradient.get(); -} - -Context PreviewDelegate::elementContext() { - return Context::History; -} - -} // namespace - -void ShowReplyToChatBox( - std::shared_ptr show, - FullReplyTo reply, - Fn clearOldDraft) { - class Controller final : public ChooseRecipientBoxController { - public: - using Chosen = not_null; - - Controller(not_null session) - : ChooseRecipientBoxController( - session, - [=](Chosen thread) mutable { _singleChosen.fire_copy(thread); }, - nullptr) { - } - - void rowClicked(not_null row) override final { - ChooseRecipientBoxController::rowClicked(row); - } - - [[nodiscard]] rpl::producer singleChosen() const{ - return _singleChosen.events(); - } - - bool respectSavedMessagesChat() const override { - return false; - } - - private: - void prepareViewHook() override { - delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); - } - - rpl::event_stream _singleChosen; - - }; - - struct State { - not_null box; - not_null controller; - base::unique_qptr menu; - }; - const auto session = &show->session(); - const auto state = [&] { - auto controller = std::make_unique(session); - const auto controllerRaw = controller.get(); - auto box = Box(std::move(controller), [=]( - not_null box) { - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - }); - const auto boxRaw = box.data(); - show->show(std::move(box)); - auto state = State{ boxRaw, controllerRaw }; - return boxRaw->lifetime().make_state(std::move(state)); - }(); - - auto chosen = [=](not_null thread) mutable { - const auto history = thread->owningHistory(); - const auto topicRootId = thread->topicRootId(); - const auto draft = history->localDraft(topicRootId); - const auto textWithTags = draft - ? draft->textWithTags - : TextWithTags(); - const auto cursor = draft ? draft->cursor : MessageCursor(); - reply.topicRootId = topicRootId; - history->setLocalDraft(std::make_unique( - textWithTags, - reply, - cursor, - Data::WebPageDraft())); - history->clearLocalEditDraft(topicRootId); - history->session().changes().entryUpdated( - thread, - Data::EntryUpdate::Flag::LocalDraftSet); - - if (clearOldDraft) { - crl::on_main(&history->session(), clearOldDraft); - } - return true; - }; - auto callback = [=, chosen = std::move(chosen)]( - Controller::Chosen thread) mutable { - const auto weak = Ui::MakeWeak(state->box); - if (!chosen(thread)) { - return; - } else if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - state->controller->singleChosen( - ) | rpl::start_with_next(std::move(callback), state->box->lifetime()); -} - -void EditReplyOptions( - std::shared_ptr show, - FullReplyTo reply, - Fn done, - Fn highlight, - Fn clearOldDraft) { - const auto session = &show->session(); - const auto item = session->data().message(reply.messageId); - if (!item) { - return; - } - show->show(Box([=](not_null box) { - box->setWidth(st::boxWideWidth); - - const auto bottom = box->setPinnedToBottomContent( - object_ptr(box)); - const auto addSkip = [&] { - const auto skip = bottom->add(object_ptr( - bottom, - st::settingsPrivacySkipTop)); - skip->paintRequest() | rpl::start_with_next([=](QRect clip) { - QPainter(skip).fillRect(clip, st::boxBg); - }, skip->lifetime()); - }; - - addSkip(); - - Settings::AddButton( - bottom, - tr::lng_reply_in_another_chat(), - st::settingsButton, - { &st::menuIconReplace } - )->setClickedCallback([=] { - ShowReplyToChatBox(show, reply, clearOldDraft); - }); - - Settings::AddButton( - bottom, - tr::lng_reply_show_in_chat(), - st::settingsButton, - { &st::menuIconShowInChat } - )->setClickedCallback(highlight); - - const auto finish = [=](FullReplyTo result) { - const auto weak = Ui::MakeWeak(box); - done(std::move(result)); - if (const auto strong = weak.data()) { - strong->closeBox(); - } - }; - - Settings::AddButton( - bottom, - tr::lng_reply_remove(), - st::settingsAttentionButtonWithIcon, - { &st::menuIconDeleteAttention } - )->setClickedCallback([=] { - finish({}); - }); - - if (!item->originalText().empty()) { - addSkip(); - Settings::AddDividerText( - bottom, - tr::lng_reply_about_quote()); - } - - struct State { - rpl::variable quote; - }; - const auto state = box->lifetime().make_state(); - state->quote = AddQuoteTracker(box, show, item, reply.quote); - - box->setTitle(reply.quote.empty() - ? tr::lng_reply_options_header() - : tr::lng_reply_options_quote()); - - auto save = state->quote.value( - ) | rpl::map([=](const TextWithEntities "e) { - return quote.empty() - ? tr::lng_settings_save() - : tr::lng_reply_quote_selected(); - }) | rpl::flatten_latest(); - box->addButton(std::move(save), [=] { - auto result = reply; - result.quote = state->quote.current(); - finish(result); - }); - - box->addButton(tr::lng_cancel(), [=] { - box->closeBox(); - }); - - session->data().itemRemoved( - ) | rpl::filter([=](not_null removed) { - return removed == item; - }) | rpl::start_with_next([=] { - finish({}); - }, box->lifetime()); - })); -} - -} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp new file mode 100644 index 000000000..909e3508e --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.cpp @@ -0,0 +1,327 @@ +/* +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/controls/history_view_webpage_processor.h" + +#include "base/unixtime.h" +#include "data/data_chat_participant_status.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "data/data_web_page.h" +#include "history/history.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" + +namespace HistoryView::Controls { + +WebPageText TitleAndDescriptionFromWebPage(not_null d) { + QString resultTitle, resultDescription; + const auto document = d->document; + const auto author = d->author; + const auto siteName = d->siteName; + const auto title = d->title; + const auto description = d->description; + const auto filenameOrUrl = [&] { + return ((document && !document->filename().isEmpty()) + ? document->filename() + : d->url); + }; + const auto authorOrFilename = [&] { + return (author.isEmpty() + ? filenameOrUrl() + : author); + }; + const auto descriptionOrAuthor = [&] { + return (description.text.isEmpty() + ? authorOrFilename() + : description.text); + }; + if (siteName.isEmpty()) { + if (title.isEmpty()) { + if (description.text.isEmpty()) { + resultTitle = author; + resultDescription = filenameOrUrl(); + } else { + resultTitle = description.text; + resultDescription = authorOrFilename(); + } + } else { + resultTitle = title; + resultDescription = descriptionOrAuthor(); + } + } else { + resultTitle = siteName; + resultDescription = title.isEmpty() + ? descriptionOrAuthor() + : title; + } + return { resultTitle, resultDescription }; +} + +bool DrawWebPageDataPreview( + QPainter &p, + not_null webpage, + not_null context, + QRect to) { + const auto document = webpage->document; + const auto photo = webpage->photo; + if ((!photo || photo->isNull()) + && (!document + || !document->hasThumbnail() + || document->isPatternWallPaper())) { + return false; + } + + const auto preview = photo + ? photo->getReplyPreview(Data::FileOrigin(), context, false) + : document->getReplyPreview(Data::FileOrigin(), context, false); + if (preview) { + const auto w = preview->width(); + const auto h = preview->height(); + if (w == h) { + p.drawPixmap(to.x(), to.y(), preview->pix()); + } else { + const auto from = (w > h) + ? QRect((w - h) / 2, 0, h, h) + : QRect(0, (h - w) / 2, w, w); + p.drawPixmap(to, preview->pix(), from); + } + } + return true; +} + +[[nodiscard]] bool ShowWebPagePreview(WebPageData *page) { + return page && !page->failed; +} + +WebPageText ProcessWebPageData(WebPageData *page) { + auto previewText = 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; +} + +WebpageProcessor::WebpageProcessor( + not_null history, + not_null field) +: _history(history) +, _api(&history->session().mtp()) +, _parser(field) +, _timer([=] { + if (!ShowWebPagePreview(_data) || _link.isEmpty()) { + return; + } + request(); +}) { + _history->session().downloaderTaskFinished( + ) | rpl::filter([=] { + return _data && (_data->document || _data->photo); + }) | rpl::start_with_next([=] { + _repaintRequests.fire({}); + }, _lifetime); + + _history->owner().webPageUpdates( + ) | rpl::filter([=](not_null page) { + return (_data == page.get()); + }) | rpl::start_with_next([=] { + updateFromData(); + }, _lifetime); + + _parser.list().changes( + ) | rpl::start_with_next([=](QStringList &&parsed) { + _parsedLinks = std::move(parsed); + checkPreview(); + }, _lifetime); +} + +rpl::producer<> WebpageProcessor::repaintRequests() const { + return _repaintRequests.events(); +} + +Data::WebPageDraft WebpageProcessor::draft() const { + return _draft; +} + +void WebpageProcessor::apply(Data::WebPageDraft draft, bool reparse) { + _api.request(base::take(_requestId)).cancel(); + if (draft.removed) { + _draft = draft; + _data = nullptr; + _links = QStringList(); + _link = QString(); + _parsed = WebpageParsed(); + updateFromData(); + } else if (draft.manual && draft.id && !draft.url.isEmpty()) { + _draft = draft; + _parsedLinks = QStringList(); + _links = QStringList(); + _link = _draft.url; + const auto page = _history->owner().webpage(draft.id); + if (page->url == draft.url) { + _data = page; + updateFromData(); + } else { + request(); + } + } else if (!draft.manual && !_draft.manual) { + _draft = draft; + checkNow(reparse); + } +} + +void WebpageProcessor::updateFromData() { + _timer.cancel(); + auto parsed = WebpageParsed(); + if (ShowWebPagePreview(_data)) { + if (const auto till = _data->pendingTill) { + parsed.drawPreview = [](QPainter &p, QRect to) { + return false; + }; + parsed.title = tr::lng_preview_loading(tr::now); + parsed.description = _link; + + const auto timeout = till - base::unixtime::now(); + _timer.callOnce( + std::max(timeout, 0) * crl::time(1000)); + } else { + const auto webpage = _data; + const auto context = _history->peer; + const auto preview = ProcessWebPageData(_data); + parsed.title = preview.title; + parsed.description = preview.description; + parsed.drawPreview = [=](QPainter &p, QRect to) { + return DrawWebPageDataPreview(p, webpage, context, to); + }; + } + } + _parsed = std::move(parsed); + _repaintRequests.fire({}); +} + +void WebpageProcessor::request() { + const auto link = _link; + const auto done = [=](const MTPDmessageMediaWebPage &data) { + const auto page = _history->owner().processWebpage(data.vwebpage()); + if (page->pendingTill > 0 + && page->pendingTill < base::unixtime::now()) { + page->pendingTill = 0; + page->failed = true; + } + _cache.emplace(link, page->failed ? nullptr : page.get()); + if (_link == link && !_draft.removed && !_draft.manual) { + _data = (page->id && !page->failed) + ? page.get() + : nullptr; + _draft.id = page->id; + _draft.url = page->url; + updateFromData(); + } + }; + const auto fail = [=] { + _cache.emplace(link, nullptr); + if (_link == link && !_draft.removed && !_draft.manual) { + _links = QStringList(); + checkPreview(); + } + }; + _requestId = _api.request( + MTPmessages_GetWebPagePreview( + MTP_flags(0), + MTP_string(_link), + MTPVector() + )).done([=](const MTPMessageMedia &result, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + result.match([=](const MTPDmessageMediaWebPage &data) { + done(data); + }, [&](const auto &d) { + fail(); + }); + }).fail([=](const MTP::Error &error, mtpRequestId requestId) { + if (_requestId == requestId) { + _requestId = 0; + } + fail(); + }).send(); +} + +void WebpageProcessor::checkNow(bool force) { + _parser.parseNow(); + if (force) { + _link = QString(); + _links = QStringList(); + if (_parsedLinks.isEmpty()) { + _data = nullptr; + updateFromData(); + return; + } + } + checkPreview(); +} + +void WebpageProcessor::checkPreview() { + const auto previewRestricted = _history->peer + && _history->peer->amRestricted(ChatRestriction::EmbedLinks); + if (_draft.removed) { + return; + } else if (previewRestricted) { + apply({ .removed = true }); + _draft.removed = false; + return; + } else if (_draft.manual) { + return; + } else if (_links == _parsedLinks) { + return; + } + _links = _parsedLinks; + + auto page = (WebPageData*)nullptr; + auto chosen = QString(); + for (const auto &link : _links) { + const auto i = _cache.find(link); + if (i == end(_cache)) { + chosen = link; + break; + } else if (i->second) { + if (i->second->failed) { + i->second = nullptr; + } else { + chosen = link; + page = i->second; + break; + } + } + } + if (_link != chosen) { + _link = chosen; + _api.request(base::take(_requestId)).cancel(); + if (!page && !_link.isEmpty()) { + request(); + } + } + if (page) { + _data = page; + _draft.id = _data->id; + _draft.url = _data->url; + } else { + _data = nullptr; + _draft = {}; + } + updateFromData(); +} + +rpl::producer WebpageProcessor::parsedValue() const { + return _parsed.value(); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h new file mode 100644 index 000000000..e51c7c838 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_webpage_processor.h @@ -0,0 +1,100 @@ +/* +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 "base/weak_ptr.h" +#include "data/data_drafts.h" +#include "chat_helpers/message_field.h" +#include "mtproto/sender.h" + +class History; + +namespace Ui { +class InputField; +} // namespace Ui + +namespace HistoryView::Controls { + +struct WebPageText { + QString title; + QString description; +}; + +[[nodiscard]] WebPageText TitleAndDescriptionFromWebPage( + not_null data); + +bool DrawWebPageDataPreview( + QPainter &p, + not_null webpage, + not_null context, + QRect to); + +[[nodiscard]] bool ShowWebPagePreview(WebPageData *page); +[[nodiscard]] WebPageText ProcessWebPageData(WebPageData *page); + +struct WebpageParsed { + Fn drawPreview; + QString title; + QString description; + + explicit operator bool() const { + return drawPreview != nullptr; + } +}; + +class WebpageProcessor final : public base::has_weak_ptr { +public: + WebpageProcessor( + not_null history, + not_null field); + + void checkNow(bool force); + + // If editing a message without a preview we don't want to show + // parsed preview until links set is changed in the message. + // + // If writing a new message we want to parse links immediately, + // unless preview was removed in the draft or manual. + void apply(Data::WebPageDraft draft, bool reparse = true); + [[nodiscard]] Data::WebPageDraft draft() const; + + [[nodiscard]] rpl::producer<> repaintRequests() const; + [[nodiscard]] rpl::producer parsedValue() const; + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void updateFromData(); + void checkPreview(); + void request(); + + const not_null _history; + MTP::Sender _api; + MessageLinksParser _parser; + + QStringList _parsedLinks; + QStringList _links; + QString _link; + WebPageData *_data = nullptr; + base::flat_map _cache; + Data::WebPageDraft _draft; + + mtpRequestId _requestId = 0; + + rpl::event_stream<> _repaintRequests; + rpl::variable _parsed; + + base::Timer _timer; + + rpl::lifetime _lifetime; + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index faf393999..1048b98b9 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_controls.h" #include "history/view/controls/history_view_forward_panel.h" -#include "history/view/controls/history_view_reply_options.h" +#include "history/view/controls/history_view_draft_options.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index c89705092..7e9ff2b35 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -244,31 +244,8 @@ QSize WebPage::countOptimalSize() { _asArticle = 0; } else if (_data->photo && (_flags & Flag::ForceSmallMedia)) { _asArticle = 1; - } else if (!_collage.empty()) { - _asArticle = 0; - } else if (!_data->document - && _data->photo - && _data->type != WebPageType::Photo - && _data->type != WebPageType::Document - && _data->type != WebPageType::Story - && _data->type != WebPageType::Video) { - if (_data->type == WebPageType::Profile) { - _asArticle = 1; - } else if (_data->siteName == u"Twitter"_q - || _data->siteName == u"Facebook"_q - || _data->type == WebPageType::ArticleWithIV) { - _asArticle = 0; - } else { - _asArticle = 1; - } - if (_asArticle - && _data->description.text.isEmpty() - && title.isEmpty() - && _data->siteName.isEmpty()) { - _asArticle = 0; - } } else { - _asArticle = 0; + _asArticle = _data->computeDefaultSmallMedia(); } // init attach diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c36559a67..b05f7eb91 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c36559a6797f02d8a56a414ac91f9c6fd08b5270 +Subproject commit b05f7eb915a86f67249904061d70f293066de618