tdesktop/Telegram/SourceFiles/history/view/history_view_compose_contro...

1052 lines
27 KiB
C++

/*
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/history_view_compose_controls.h"
#include "base/event_filter.h"
#include "base/qt_signal_producer.h"
#include "base/unixtime.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_section.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_changes.h"
#include "data/data_messages.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "facades.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_webpage_preview.h"
#include "inline_bots/inline_results_widget.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "styles/style_history.h"
#include "ui/special_buttons.h"
#include "ui/text_options.h"
#include "ui/ui_utility.h"
#include "ui/widgets/input_fields.h"
#include "window/window_session_controller.h"
namespace HistoryView {
namespace {
using MessageToEdit = ComposeControls::MessageToEdit;
constexpr auto kMouseEvent = {
QEvent::MouseMove,
QEvent::MouseButtonPress,
QEvent::MouseButtonRelease
};
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
return page && (page->pendingTill >= 0);
}
WebPageText ProcessWebPageData(WebPageData *page) {
auto previewText = HistoryView::TitleAndDescriptionFromWebPage(page);
if (previewText.title.isEmpty()) {
if (page->document) {
previewText.title = tr::lng_attach_file(tr::now);
} else if (page->photo) {
previewText.title = tr::lng_attach_photo(tr::now);
}
}
return previewText;
}
} // namespace
class FieldHeader : public Ui::RpWidget {
public:
FieldHeader(QWidget *parent, not_null<Data::Session*> data);
void init();
void editMessage(FullMsgId edit);
void previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page);
bool isDisplayed() const;
bool isEditingMessage() const;
rpl::producer<FullMsgId> editMsgId() const;
rpl::producer<FullMsgId> scrollToItemRequests() const;
MessageToEdit queryToEdit();
WebPageId webPageId() const;
rpl::producer<bool> visibleChanged();
protected:
private:
void updateControlsGeometry(QSize size);
void updateVisible();
void paintWebPage(Painter &p);
void paintEditMessage(Painter &p);
struct Preview {
WebPageData *data = nullptr;
Ui::Text::String title;
Ui::Text::String description;
};
rpl::variable<QString> _title;
rpl::variable<QString> _description;
Preview _preview;
bool hasPreview() const;
Ui::Text::String _editMsgText;
rpl::variable<FullMsgId> _editMsgId;
const not_null<Data::Session*> _data;
const not_null<Ui::IconButton*> _cancel;
rpl::event_stream<bool> _visibleChanged;
rpl::event_stream<FullMsgId> _scrollToItemRequests;
};
FieldHeader::FieldHeader(QWidget *parent, not_null<Data::Session*> data)
: RpWidget(parent)
, _data(data)
, _cancel(Ui::CreateChild<Ui::IconButton>(this, st::historyReplyCancel)) {
resize(QSize(parent->width(), st::historyReplyHeight));
init();
}
void FieldHeader::init() {
sizeValue(
) | rpl::start_with_next([=](QSize size) {
updateControlsGeometry(size);
}, lifetime());
const auto leftIconPressed = lifetime().make_state<bool>(false);
paintRequest(
) | rpl::start_with_next([=] {
Painter p(this);
p.fillRect(rect(), st::historyComposeAreaBg);
if (isEditingMessage()) {
const auto position = st::historyReplyIconPosition;
st::historyEditIcon.paint(p, position, width());
}
(!ShowWebPagePreview(_preview.data) || *leftIconPressed)
? paintEditMessage(p)
: paintWebPage(p);
}, lifetime());
const auto checkPreview = [=](not_null<const HistoryItem*> item) {
_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;
}
}
};
_editMsgId.value(
) | rpl::start_with_next([=] {
updateVisible();
if (const auto item = _data->message(_editMsgId.current())) {
_editMsgText.setText(
st::messageTextStyle,
item->inReplyText(),
Ui::DialogTextOptions());
checkPreview(item);
}
}, lifetime());
_cancel->addClickHandler([=] {
if (hasPreview()) {
_preview = {};
update();
} else {
_editMsgId = {};
}
updateVisible();
});
_title.value(
) | rpl::start_with_next([=](const auto &t) {
_preview.title.setText(
st::msgNameStyle,
t,
Ui::NameTextOptions());
}, lifetime());
_description.value(
) | rpl::start_with_next([=](const auto &d) {
_preview.description.setText(
st::messageTextStyle,
TextUtilities::Clean(d),
Ui::DialogTextOptions());
}, lifetime());
setMouseTracking(true);
const auto inClickable = lifetime().make_state<bool>(false);
events(
) | rpl::filter([=](not_null<QEvent*> event) {
return ranges::contains(kMouseEvent, event->type())
&& isEditingMessage();
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
const auto type = event->type();
const auto e = static_cast<QMouseEvent*>(event.get());
const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
const auto inPreviewRect = QRect(
st::historyReplySkip,
0,
width() - st::historyReplySkip - _cancel->width(),
height()).contains(pos);
if (type == QEvent::MouseMove) {
const auto inEdit = inPreviewRect;
if (inEdit != *inClickable) {
*inClickable = inEdit;
setCursor(*inClickable
? style::cur_pointer
: style::cur_default);
}
return;
}
const auto isLeftIcon = (pos.x() < st::historyReplySkip);
const auto isLeftButton = (e->button() == Qt::LeftButton);
if (type == QEvent::MouseButtonPress) {
if (isLeftButton && isLeftIcon) {
*leftIconPressed = true;
update();
} else if (isLeftButton && inPreviewRect) {
_scrollToItemRequests.fire(_editMsgId.current());
}
} else if (type == QEvent::MouseButtonRelease) {
if (isLeftButton && *leftIconPressed) {
*leftIconPressed = false;
update();
}
}
}, lifetime());
}
void FieldHeader::previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page) {
std::move(
title
) | rpl::start_with_next([=](const QString &t) {
_title = t;
}, lifetime());
std::move(
description
) | rpl::start_with_next([=](const QString &d) {
_description = d;
}, lifetime());
std::move(
page
) | rpl::start_with_next([=](WebPageData *p) {
_preview.data = p;
updateVisible();
}, lifetime());
}
void FieldHeader::paintWebPage(Painter &p) {
Expects(ShowWebPagePreview(_preview.data));
const auto textTop = st::msgReplyPadding.top();
auto previewLeft = st::historyReplySkip + st::webPageLeft;
p.fillRect(
st::historyReplySkip,
textTop,
st::webPageBar,
st::msgReplyBarSize.height(),
st::msgInReplyBarColor);
const QRect to(
previewLeft,
textTop,
st::msgReplyBarSize.height(),
st::msgReplyBarSize.height());
if (HistoryView::DrawWebPageDataPreview(p, _preview.data, to)) {
previewLeft += st::msgReplyBarSize.height()
+ st::msgReplyBarSkip
- st::msgReplyBarSize.width()
- st::msgReplyBarPos.x();
}
const auto elidedWidth = width()
- previewLeft
- _cancel->width()
- st::msgReplyPadding.right();
p.setPen(st::historyReplyNameFg);
_preview.title.drawElided(
p,
previewLeft,
textTop,
elidedWidth);
p.setPen(st::historyComposeAreaFg);
_preview.description.drawElided(
p,
previewLeft,
textTop + st::msgServiceNameFont->height,
elidedWidth);
}
void FieldHeader::paintEditMessage(Painter &p) {
const auto replySkip = st::historyReplySkip;
p.setPen(st::historyReplyNameFg);
p.setFont(st::msgServiceNameFont);
p.drawTextLeft(
replySkip,
st::msgReplyPadding.top(),
width(),
tr::lng_edit_message(tr::now));
p.setPen(st::historyComposeAreaFg);
p.setTextPalette(st::historyComposeAreaPalette);
_editMsgText.drawElided(
p,
replySkip,
st::msgReplyPadding.top() + st::msgServiceNameFont->height,
width() - replySkip - _cancel->width() - st::msgReplyPadding.right());
p.restoreTextPalette();
}
void FieldHeader::updateVisible() {
isDisplayed() ? show() : hide();
_visibleChanged.fire(isVisible());
}
rpl::producer<bool> FieldHeader::visibleChanged() {
return _visibleChanged.events();
}
bool FieldHeader::isDisplayed() const {
return isEditingMessage() || hasPreview();
}
bool FieldHeader::isEditingMessage() const {
return !!_editMsgId.current();
}
bool FieldHeader::hasPreview() const {
return _preview.data != nullptr;
}
WebPageId FieldHeader::webPageId() const {
return hasPreview() ? _preview.data->id : CancelledWebPageId;
}
void FieldHeader::updateControlsGeometry(QSize size) {
_cancel->moveToRight(0, 0);
}
void FieldHeader::editMessage(FullMsgId id) {
_editMsgId = id;
}
rpl::producer<FullMsgId> FieldHeader::editMsgId() const {
return _editMsgId.value();
}
rpl::producer<FullMsgId> FieldHeader::scrollToItemRequests() const {
return _scrollToItemRequests.events();
}
MessageToEdit FieldHeader::queryToEdit() {
const auto item = _data->message(_editMsgId.current());
if (!isEditingMessage() || !item) {
return {};
}
return {
item->fullId(),
{
item->isScheduled() ? item->date() : 0,
false,
false,
!hasPreview(),
},
};
}
ComposeControls::ComposeControls(
not_null<QWidget*> parent,
not_null<Window::SessionController*> window,
Mode mode)
: _parent(parent)
, _window(window)
//, _mode(mode)
, _wrap(std::make_unique<Ui::RpWidget>(parent))
, _send(Ui::CreateChild<Ui::SendButton>(_wrap.get()))
, _attachToggle(Ui::CreateChild<Ui::IconButton>(
_wrap.get(),
st::historyAttach))
, _tabbedSelectorToggle(Ui::CreateChild<Ui::EmojiButton>(
_wrap.get(),
st::historyAttachEmoji))
, _field(
Ui::CreateChild<Ui::InputField>(
_wrap.get(),
st::historyComposeField,
Ui::InputField::Mode::MultiLine,
tr::lng_message_ph()))
, _header(std::make_unique<FieldHeader>(
_wrap.get(),
&_window->session().data())) {
init();
}
ComposeControls::~ComposeControls() {
setTabbedPanel(nullptr);
}
Main::Session &ComposeControls::session() const {
return _window->session();
}
void ComposeControls::setHistory(History *history) {
if (_history == history) {
return;
}
_history = history;
_window->tabbedSelector()->setCurrentPeer(
history ? history->peer.get() : nullptr);
initWebpageProcess();
}
void ComposeControls::move(int x, int y) {
_wrap->move(x, y);
}
void ComposeControls::resizeToWidth(int width) {
_wrap->resizeToWidth(width);
updateHeight();
}
rpl::producer<int> ComposeControls::height() const {
return _wrap->heightValue();
}
int ComposeControls::heightCurrent() const {
return _wrap->height();
}
void ComposeControls::focus() {
_field->setFocus();
}
rpl::producer<> ComposeControls::cancelRequests() const {
return _cancelRequests.events();
}
rpl::producer<not_null<QKeyEvent*>> ComposeControls::keyEvents() const {
return _wrap->events(
) | rpl::map([=](not_null<QEvent*> e) -> not_null<QKeyEvent*> {
return static_cast<QKeyEvent*>(e.get());
}) | rpl::filter([=](not_null<QEvent*> event) {
return (event->type() == QEvent::KeyPress);
});
}
rpl::producer<> ComposeControls::sendRequests() const {
auto filter = rpl::filter([=] {
return _send->type() == Ui::SendButton::Type::Schedule;
});
auto submits = base::qt_signal_producer(
_field.get(),
&Ui::InputField::submitted);
return rpl::merge(
_send->clicks() | filter | rpl::to_empty,
std::move(submits) | filter | rpl::to_empty);
}
rpl::producer<MessageToEdit> ComposeControls::editRequests() const {
auto toValue = rpl::map([=] { return _header->queryToEdit(); });
auto filter = rpl::filter([=] {
return _send->type() == Ui::SendButton::Type::Save;
});
auto submits = base::qt_signal_producer(
_field.get(),
&Ui::InputField::submitted);
return rpl::merge(
_send->clicks() | filter | toValue,
std::move(submits) | filter | toValue);
}
rpl::producer<> ComposeControls::attachRequests() const {
return _attachToggle->clicks() | rpl::to_empty;
}
void ComposeControls::setMimeDataHook(MimeDataHook hook) {
_field->setMimeDataHook(std::move(hook));
}
rpl::producer<not_null<DocumentData*>> ComposeControls::fileChosen() const {
return _fileChosen.events();
}
rpl::producer<not_null<PhotoData*>> ComposeControls::photoChosen() const {
return _photoChosen.events();
}
auto ComposeControls::inlineResultChosen() const
->rpl::producer<ChatHelpers::TabbedSelector::InlineChosen> {
return _inlineResultChosen.events();
}
void ComposeControls::showStarted() {
if (_inlineResults) {
_inlineResults->hideFast();
}
if (_tabbedPanel) {
_tabbedPanel->hideFast();
}
_wrap->hide();
}
void ComposeControls::showFinished() {
if (_inlineResults) {
_inlineResults->hideFast();
}
if (_tabbedPanel) {
_tabbedPanel->hideFast();
}
_wrap->show();
}
void ComposeControls::showForGrab() {
showFinished();
}
TextWithTags ComposeControls::getTextWithAppliedMarkdown() const {
return _field->getTextWithAppliedMarkdown();
}
void ComposeControls::clear() {
setText(TextWithTags());
}
void ComposeControls::setText(const TextWithTags &textWithTags) {
//_textUpdateEvents = events;
_field->setTextWithTags(textWithTags, Ui::InputField::HistoryAction::Clear/*fieldHistoryAction*/);
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
//_textUpdateEvents = TextUpdateEvent::SaveDraft
// | TextUpdateEvent::SendTyping;
//previewCancel();
//_previewCancelled = false;
}
void ComposeControls::hidePanelsAnimated() {
//_fieldAutocomplete->hideAnimated();
if (_tabbedPanel) {
_tabbedPanel->hideAnimated();
}
if (_inlineResults) {
_inlineResults->hideAnimated();
}
}
void ComposeControls::init() {
initField();
initTabbedSelector();
initSendButton();
_wrap->sizeValue(
) | rpl::start_with_next([=](QSize size) {
updateControlsGeometry(size);
}, _wrap->lifetime());
_wrap->geometryValue(
) | rpl::start_with_next([=](QRect rect) {
updateOuterGeometry(rect);
}, _wrap->lifetime());
_wrap->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
paintBackground(clip);
}, _wrap->lifetime());
_header->editMsgId(
) | rpl::start_with_next([=](const auto &id) {
if (_header->isEditingMessage()) {
setTextFromEditingMessage(_window->session().data().message(id));
} else {
setText(_localSavedText);
_localSavedText = {};
}
updateSendButtonType();
}, _wrap->lifetime());
_header->visibleChanged(
) | rpl::start_with_next([=] {
updateHeight();
}, _wrap->lifetime());
{
const auto lastMsgId = _wrap->lifetime().make_state<FullMsgId>();
_header->editMsgId(
) | rpl::filter([=](const auto &id) {
return !!id;
}) | rpl::start_with_next([=](const auto &id) {
*lastMsgId = id;
}, _wrap->lifetime());
_window->session().data().itemRemoved(
) | rpl::filter([=](not_null<const HistoryItem*> item) {
return item->id && ((*lastMsgId) == item->fullId());
}) | rpl::start_with_next([=] {
cancelEditMessage();
}, _wrap->lifetime());
}
}
void ComposeControls::setTextFromEditingMessage(not_null<HistoryItem*> item) {
if (!_header->isEditingMessage()) {
return;
}
_localSavedText = getTextWithAppliedMarkdown();
const auto t = item->originalText();
const auto text = TextWithTags{
t.text,
TextUtilities::ConvertEntitiesToTextTags(t.entities)
};
setText(text);
}
void ComposeControls::initField() {
_field->setMaxHeight(st::historyComposeFieldMaxHeight);
_field->setSubmitSettings(Core::App().settings().sendSubmitWay());
//Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); });
Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); });
//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); });
//Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); });
//Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
InitMessageField(_window, _field);
const auto suggestions = Ui::Emoji::SuggestionsController::Init(
_parent,
_field,
&_window->session());
_raiseEmojiSuggestions = [=] { suggestions->raise(); };
}
void ComposeControls::initTabbedSelector() {
if (_window->hasTabbedSelectorOwnership()) {
createTabbedPanel();
} else {
setTabbedPanel(nullptr);
}
_tabbedSelectorToggle->addClickHandler([=] {
toggleTabbedSelectorMode();
});
const auto selector = _window->tabbedSelector();
const auto wrap = _wrap.get();
base::install_event_filter(wrap, selector, [=](not_null<QEvent*> e) {
if (_tabbedPanel && e->type() == QEvent::ParentChange) {
setTabbedPanel(nullptr);
}
return base::EventFilterResult::Continue;
});
selector->emojiChosen(
) | rpl::start_with_next([=](EmojiPtr emoji) {
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
}, wrap->lifetime());
selector->fileChosen(
) | rpl::start_to_stream(_fileChosen, wrap->lifetime());
selector->photoChosen(
) | rpl::start_to_stream(_photoChosen, wrap->lifetime());
selector->inlineResultChosen(
) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
}
void ComposeControls::initSendButton() {
updateSendButtonType();
_send->finishAnimating();
}
void ComposeControls::updateSendButtonType() {
using Type = Ui::SendButton::Type;
const auto type = [&] {
if (_header->isEditingMessage()) {
return Type::Save;
//} else if (_isInlineBot) {
// return Type::Cancel;
//} else if (showRecordButton()) {
// return Type::Record;
}
return Type::Schedule;
}();
_send->setType(type);
const auto delay = [&] {
return /*(type != Type::Cancel && type != Type::Save && _peer)
? _peer->slowmodeSecondsLeft()
: */0;
}();
_send->setSlowmodeDelay(delay);
//_send->setDisabled(_peer
// && _peer->slowmodeApplied()
// && (_history->latestSendingMessage() != nullptr)
// && (type == Type::Send || type == Type::Record));
//if (delay != 0) {
// base::call_delayed(
// kRefreshSlowmodeLabelTimeout,
// this,
// [=] { updateSendButtonType(); });
//}
}
void ComposeControls::updateControlsGeometry(QSize size) {
// _attachToggle -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel
// (_attachDocument|_attachPhoto) _field _tabbedSelectorToggle _send
const auto fieldWidth = size.width()
- _attachToggle->width()
- st::historySendRight
- _send->width()
- _tabbedSelectorToggle->width();
_field->resizeToWidth(fieldWidth);
const auto buttonsTop = size.height() - _attachToggle->height();
auto left = 0;
_attachToggle->moveToLeft(left, buttonsTop);
left += _attachToggle->width();
_field->moveToLeft(
left,
size.height() - _field->height() - st::historySendPadding);
_header->resizeToWidth(size.width());
_header->moveToLeft(
0,
_field->y() - _header->height() - st::historySendPadding);
auto right = st::historySendRight;
_send->moveToRight(right, buttonsTop);
right += _send->width();
_tabbedSelectorToggle->moveToRight(right, buttonsTop);
}
void ComposeControls::updateOuterGeometry(QRect rect) {
if (_inlineResults) {
_inlineResults->moveBottom(rect.y());
}
if (_tabbedPanel) {
_tabbedPanel->moveBottomRight(
rect.y() + rect.height() - _attachToggle->height(),
rect.x() + rect.width());
}
}
void ComposeControls::paintBackground(QRect clip) {
Painter p(_wrap.get());
p.fillRect(clip, st::historyComposeAreaBg);
}
void ComposeControls::escape() {
_cancelRequests.fire({});
}
bool ComposeControls::pushTabbedSelectorToThirdSection(
not_null<PeerData*> peer,
const Window::SectionShow &params) {
if (!_tabbedPanel) {
return true;
//} else if (!_canSendMessages) {
// Core::App().settings().setTabbedReplacedWithInfo(true);
// _window->showPeerInfo(_peer, params.withThirdColumn());
// return;
}
Core::App().settings().setTabbedReplacedWithInfo(false);
_tabbedSelectorToggle->setColorOverrides(
&st::historyAttachEmojiActive,
&st::historyRecordVoiceFgActive,
&st::historyRecordVoiceRippleBgActive);
_window->resizeForThirdSection();
_window->showSection(
ChatHelpers::TabbedMemento(),
params.withThirdColumn());
return true;
}
bool ComposeControls::returnTabbedSelector() {
createTabbedPanel();
updateOuterGeometry(_wrap->geometry());
return true;
}
void ComposeControls::createTabbedPanel() {
setTabbedPanel(std::make_unique<ChatHelpers::TabbedPanel>(
_parent,
_window,
_window->tabbedSelector()));
}
void ComposeControls::setTabbedPanel(
std::unique_ptr<ChatHelpers::TabbedPanel> panel) {
_tabbedPanel = std::move(panel);
if (const auto raw = _tabbedPanel.get()) {
_tabbedSelectorToggle->installEventFilter(raw);
_tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
} else {
_tabbedSelectorToggle->setColorOverrides(
&st::historyAttachEmojiActive,
&st::historyRecordVoiceFgActive,
&st::historyRecordVoiceRippleBgActive);
}
}
void ComposeControls::toggleTabbedSelectorMode() {
if (!_history) {
return;
}
if (_tabbedPanel) {
if (_window->canShowThirdSection() && !Adaptive::OneColumn()) {
Core::App().settings().setTabbedSelectorSectionEnabled(true);
Core::App().saveSettingsDelayed();
pushTabbedSelectorToThirdSection(
_history->peer,
Window::SectionShow::Way::ClearStack);
} else {
_tabbedPanel->toggleAnimated();
}
} else {
_window->closeThirdSection();
}
}
void ComposeControls::updateHeight() {
const auto height = _field->height()
+ (_header->isDisplayed() ? _header->height() : 0)
+ 2 * st::historySendPadding;
if (height != _wrap->height()) {
_wrap->resize(_wrap->width(), height);
}
}
void ComposeControls::editMessage(FullMsgId edit) {
cancelEditMessage();
_header->editMessage(std::move(edit));
}
void ComposeControls::cancelEditMessage() {
_header->editMessage({});
}
void ComposeControls::initWebpageProcess() {
Expects(_history);
const auto peer = _history->peer;
auto &lifetime = _wrap->lifetime();
const auto requestRepaint = crl::guard(_header.get(), [=] {
_header->update();
});
const auto parsedLinks = lifetime.make_state<QStringList>();
const auto previewLinks = lifetime.make_state<QString>();
const auto previewData = lifetime.make_state<WebPageData*>(nullptr);
using PreviewCache = std::map<QString, WebPageId>;
const auto previewCache = lifetime.make_state<PreviewCache>();
const auto previewRequest = lifetime.make_state<mtpRequestId>(0);
const auto previewCancelled = lifetime.make_state<bool>(false);
const auto mtpSender =
lifetime.make_state<MTP::Sender>(&_window->session().mtp());
const auto title = std::make_shared<rpl::event_stream<QString>>();
const auto description = std::make_shared<rpl::event_stream<QString>>();
const auto pageData = std::make_shared<rpl::event_stream<WebPageData*>>();
const auto previewTimer = lifetime.make_state<base::Timer>();
const auto updatePreview = [=] {
previewTimer->cancel();
auto t = QString();
auto d = QString();
if (ShowWebPagePreview(*previewData)) {
if (const auto till = (*previewData)->pendingTill) {
t = tr::lng_preview_loading(tr::now);
d = (*previewLinks).splitRef(' ').at(0).toString();
const auto timeout = till - base::unixtime::now();
previewTimer->callOnce(
std::max(timeout, 0) * crl::time(1000));
} else {
const auto preview = ProcessWebPageData(*previewData);
t = preview.title;
d = preview.description;
}
}
title->fire_copy(t);
description->fire_copy(d);
pageData->fire_copy(*previewData);
requestRepaint();
};
const auto gotPreview = crl::guard(_wrap.get(), [=](
const auto &result,
QString links) {
if (*previewRequest) {
*previewRequest = 0;
}
result.match([=](const MTPDmessageMediaWebPage &d) {
const auto page = _history->owner().processWebpage(d.vwebpage());
previewCache->insert({ links, page->id });
auto &till = page->pendingTill;
if (till > 0 && till <= base::unixtime::now()) {
till = -1;
}
if (links == *previewLinks && !*previewCancelled) {
*previewData = (page->id && page->pendingTill >= 0)
? page.get()
: nullptr;
updatePreview();
}
}, [=](const MTPDmessageMediaEmpty &d) {
previewCache->insert({ links, 0 });
if (links == *previewLinks && !*previewCancelled) {
*previewData = nullptr;
updatePreview();
}
}, [](const auto &d) {
});
});
const auto previewCancel = [=] {
mtpSender->request(base::take(*previewRequest)).cancel();
*previewData = nullptr;
previewLinks->clear();
updatePreview();
};
const auto getWebPagePreview = [=] {
const auto links = *previewLinks;
*previewRequest = mtpSender->request(MTPmessages_GetWebPagePreview(
MTP_flags(0),
MTP_string(links),
MTPVector<MTPMessageEntity>()
)).done([=](const MTPMessageMedia &result) {
gotPreview(result, links);
}).send();
};
const auto checkPreview = crl::guard(_wrap.get(), [=] {
const auto previewRestricted = peer
&& peer->amRestricted(ChatRestriction::f_embed_links);
if (/*_previewCancelled ||*/ previewRestricted) {
previewCancel();
return;
}
const auto newLinks = parsedLinks->join(' ');
if (*previewLinks == newLinks) {
return;
}
mtpSender->request(base::take(*previewRequest)).cancel();
*previewLinks = newLinks;
if (previewLinks->isEmpty()) {
if (ShowWebPagePreview(*previewData)) {
previewCancel();
}
} else {
const auto i = previewCache->find(*previewLinks);
if (i == previewCache->end()) {
getWebPagePreview();
} else if (i->second) {
*previewData = _history->owner().webpage(i->second);
updatePreview();
} else if (ShowWebPagePreview(*previewData)) {
previewCancel();
}
}
});
previewTimer->setCallback([=] {
if (!ShowWebPagePreview(*previewData) || previewLinks->isEmpty()) {
return;
}
getWebPagePreview();
});
_window->session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == peer);
}) | rpl::start_with_next(checkPreview, lifetime);
_window->session().downloaderTaskFinished(
) | rpl::filter([=] {
return (*previewData)
&& ((*previewData)->document || (*previewData)->photo);
}) | rpl::start_with_next((
requestRepaint
), lifetime);
const auto fieldLinksParser =
lifetime.make_state<MessageLinksParser>(_field);
fieldLinksParser->list().changes(
) | rpl::start_with_next([=](QStringList &&parsed) {
*parsedLinks = std::move(parsed);
checkPreview();
}, lifetime);
_header->previewRequested(
title->events(),
description->events(),
pageData->events());
}
WebPageId ComposeControls::webPageId() const {
return _header->webPageId();
}
rpl::producer<Data::MessagePosition> ComposeControls::scrollRequests() const {
return _header->scrollToItemRequests(
) | rpl::map([=](FullMsgId id) -> Data::MessagePosition {
if (const auto item = _window->session().data().message(id)) {
return item->position();
}
return {};
});
}
bool ComposeControls::isEditingMessage() const {
return _header->isEditingMessage();
}
} // namespace HistoryView