mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Proof of concept webview attach menu support.
This commit is contained in:
parent
841da9dde8
commit
1d8aac26ce
8 changed files with 706 additions and 56 deletions
|
@ -1631,7 +1631,7 @@ messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int
|
||||||
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
|
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
|
||||||
messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
|
messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
|
||||||
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
|
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
|
||||||
messages.addBotToAttachMenu#f319bd48 bot:InputUser = Bool;
|
messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
|
||||||
messages.requestWebView#a17c10db flags:# peer:InputPeer bot:InputUser url:flags.0?string theme_params:flags.1?DataJSON = WebViewResult;
|
messages.requestWebView#a17c10db flags:# peer:InputPeer bot:InputUser url:flags.0?string theme_params:flags.1?DataJSON = WebViewResult;
|
||||||
messages.setWebViewResult#e41cd11d query_id:long result:InputBotInlineResult = Bool;
|
messages.setWebViewResult#e41cd11d query_id:long result:InputBotInlineResult = Bool;
|
||||||
messages.getWebViewResult#22b6c214 peer:InputPeer bot:InputUser query_id:long = messages.WebViewResult;
|
messages.getWebViewResult#22b6c214 peer:InputPeer bot:InputUser query_id:long = messages.WebViewResult;
|
||||||
|
|
|
@ -1021,8 +1021,17 @@ void HistoryWidget::initTabbedSelector() {
|
||||||
sendExistingPhoto(data.photo, data.options);
|
sendExistingPhoto(data.photo, data.options);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
selector->inlineResultChosen(
|
rpl::merge(
|
||||||
) | filter | rpl::start_with_next([=](Selector::InlineChosen data) {
|
selector->inlineResultChosen(),
|
||||||
|
controller()->inlineResultConfirmed()
|
||||||
|
) | filter | rpl::filter([=](const Selector::InlineChosen &data) {
|
||||||
|
if (!data.recipientOverride) {
|
||||||
|
return true;
|
||||||
|
} else if (data.recipientOverride != _peer) {
|
||||||
|
showHistory(data.recipientOverride->id, ShowAtTheEndMsgId);
|
||||||
|
}
|
||||||
|
return (data.recipientOverride == _peer);
|
||||||
|
}) | rpl::start_with_next([=](Selector::InlineChosen data) {
|
||||||
sendInlineResult(data);
|
sendInlineResult(data);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,7 @@ private:
|
||||||
struct ResultSelected {
|
struct ResultSelected {
|
||||||
not_null<Result*> result;
|
not_null<Result*> result;
|
||||||
not_null<UserData*> bot;
|
not_null<UserData*> bot;
|
||||||
|
PeerData *recipientOverride = nullptr;
|
||||||
Api::SendOptions options;
|
Api::SendOptions options;
|
||||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||||
// Open in OverlayWidget;
|
// Open in OverlayWidget;
|
||||||
|
|
437
Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp
Normal file
437
Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "ui/chat/attach/attach_bot_webview.h"
|
||||||
|
|
||||||
|
#include "core/file_utilities.h"
|
||||||
|
#include "ui/effects/radial_animation.h"
|
||||||
|
#include "ui/layers/box_content.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/widgets/separate_panel.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/wrap/fade_wrap.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "webview/webview_embed.h"
|
||||||
|
#include "webview/webview_interface.h"
|
||||||
|
#include "base/debug_log.h"
|
||||||
|
#include "styles/style_payments.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
#include <QtCore/QJsonArray>
|
||||||
|
|
||||||
|
namespace Ui::BotWebView {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kProgressDuration = crl::time(200);
|
||||||
|
constexpr auto kProgressOpacity = 0.3;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct Panel::Progress {
|
||||||
|
Progress(QWidget *parent, Fn<QRect()> rect);
|
||||||
|
|
||||||
|
RpWidget widget;
|
||||||
|
InfiniteRadialAnimation animation;
|
||||||
|
Animations::Simple shownAnimation;
|
||||||
|
bool shown = true;
|
||||||
|
rpl::lifetime geometryLifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Panel::WebviewWithLifetime {
|
||||||
|
WebviewWithLifetime(
|
||||||
|
QWidget *parent = nullptr,
|
||||||
|
Webview::WindowConfig config = Webview::WindowConfig());
|
||||||
|
|
||||||
|
Webview::Window window;
|
||||||
|
QPointer<RpWidget> lastHidingBox;
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel::WebviewWithLifetime::WebviewWithLifetime(
|
||||||
|
QWidget *parent,
|
||||||
|
Webview::WindowConfig config)
|
||||||
|
: window(parent, std::move(config)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::Progress::Progress(QWidget *parent, Fn<QRect()> rect)
|
||||||
|
: widget(parent)
|
||||||
|
, animation(
|
||||||
|
[=] { if (!anim::Disabled()) widget.update(rect()); },
|
||||||
|
st::paymentsLoading) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::Panel(const QString &userDataPath, Fn<void()> send, Fn<void()> close)
|
||||||
|
: _userDataPath(userDataPath)
|
||||||
|
, _send(std::move(send))
|
||||||
|
, _close(std::move(close))
|
||||||
|
, _widget(std::make_unique<SeparatePanel>()) {
|
||||||
|
_widget->setInnerSize(st::paymentsPanelSize);
|
||||||
|
_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
|
||||||
|
|
||||||
|
_widget->closeRequests(
|
||||||
|
) | rpl::start_with_next(close, _widget->lifetime());
|
||||||
|
|
||||||
|
_widget->closeEvents(
|
||||||
|
) | rpl::start_with_next(close, _widget->lifetime());
|
||||||
|
|
||||||
|
setTitle(rpl::single(u"Title"_q));
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::~Panel() {
|
||||||
|
_webview = nullptr;
|
||||||
|
_progress = nullptr;
|
||||||
|
_widget = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::requestActivate() {
|
||||||
|
_widget->showAndActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::toggleProgress(bool shown) {
|
||||||
|
if (!_progress) {
|
||||||
|
if (!shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_progress = std::make_unique<Progress>(
|
||||||
|
_widget.get(),
|
||||||
|
[=] { return progressRect(); });
|
||||||
|
_progress->widget.paintRequest(
|
||||||
|
) | rpl::start_with_next([=](QRect clip) {
|
||||||
|
auto p = QPainter(&_progress->widget);
|
||||||
|
p.setOpacity(
|
||||||
|
_progress->shownAnimation.value(_progress->shown ? 1. : 0.));
|
||||||
|
auto thickness = st::paymentsLoading.thickness;
|
||||||
|
if (progressWithBackground()) {
|
||||||
|
auto color = st::windowBg->c;
|
||||||
|
color.setAlphaF(kProgressOpacity);
|
||||||
|
p.fillRect(clip, color);
|
||||||
|
}
|
||||||
|
const auto rect = progressRect().marginsRemoved(
|
||||||
|
{ thickness, thickness, thickness, thickness });
|
||||||
|
InfiniteRadialAnimation::Draw(
|
||||||
|
p,
|
||||||
|
_progress->animation.computeState(),
|
||||||
|
rect.topLeft(),
|
||||||
|
rect.size() - QSize(),
|
||||||
|
_progress->widget.width(),
|
||||||
|
st::paymentsLoading.color,
|
||||||
|
thickness);
|
||||||
|
}, _progress->widget.lifetime());
|
||||||
|
_progress->widget.show();
|
||||||
|
_progress->animation.start();
|
||||||
|
} else if (_progress->shown == shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto callback = [=] {
|
||||||
|
if (!_progress->shownAnimation.animating() && !_progress->shown) {
|
||||||
|
_progress = nullptr;
|
||||||
|
} else {
|
||||||
|
_progress->widget.update();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_progress->shown = shown;
|
||||||
|
_progress->shownAnimation.start(
|
||||||
|
callback,
|
||||||
|
shown ? 0. : 1.,
|
||||||
|
shown ? 1. : 0.,
|
||||||
|
kProgressDuration);
|
||||||
|
if (shown) {
|
||||||
|
setupProgressGeometry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Panel::progressWithBackground() const {
|
||||||
|
return (_progress->widget.width() == _widget->innerGeometry().width());
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect Panel::progressRect() const {
|
||||||
|
const auto rect = _progress->widget.rect();
|
||||||
|
if (!progressWithBackground()) {
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
const auto size = st::defaultBoxButton.height;
|
||||||
|
return QRect(
|
||||||
|
rect.x() + (rect.width() - size) / 2,
|
||||||
|
rect.y() + (rect.height() - size) / 2,
|
||||||
|
size,
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::setupProgressGeometry() {
|
||||||
|
if (!_progress || !_progress->shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_progress->geometryLifetime.destroy();
|
||||||
|
if (_webviewBottom) {
|
||||||
|
_webviewBottom->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](QRect bottom) {
|
||||||
|
const auto height = bottom.height();
|
||||||
|
const auto size = st::paymentsLoading.size;
|
||||||
|
const auto skip = (height - size.height()) / 2;
|
||||||
|
const auto inner = _widget->innerGeometry();
|
||||||
|
const auto right = inner.x() + inner.width();
|
||||||
|
const auto top = inner.y() + inner.height() - height;
|
||||||
|
// This doesn't work, because first we get the correct bottom
|
||||||
|
// geometry and after that we get the previous event (which
|
||||||
|
// triggered the 'fire' of correct geometry before getting here).
|
||||||
|
//const auto right = bottom.x() + bottom.width();
|
||||||
|
//const auto top = bottom.y();
|
||||||
|
_progress->widget.setGeometry(QRect{
|
||||||
|
QPoint(right - skip - size.width(), top + skip),
|
||||||
|
size });
|
||||||
|
}, _progress->geometryLifetime);
|
||||||
|
}
|
||||||
|
_progress->widget.show();
|
||||||
|
_progress->widget.raise();
|
||||||
|
if (_progress->shown) {
|
||||||
|
_progress->widget.setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showWebviewProgress() {
|
||||||
|
if (_webviewProgress && _progress && _progress->shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_webviewProgress = true;
|
||||||
|
toggleProgress(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::hideWebviewProgress() {
|
||||||
|
if (!_webviewProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_webviewProgress = false;
|
||||||
|
toggleProgress(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Panel::showWebview(
|
||||||
|
const QString &url,
|
||||||
|
rpl::producer<QString> bottomText) {
|
||||||
|
if (!_webview && !createWebview()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto allowBack = false;
|
||||||
|
showWebviewProgress();
|
||||||
|
_widget->destroyLayer();
|
||||||
|
_webview->window.navigate(url);
|
||||||
|
_widget->setBackAllowed(allowBack);
|
||||||
|
if (bottomText) {
|
||||||
|
const auto &padding = st::paymentsPanelPadding;
|
||||||
|
const auto label = CreateChild<FlatLabel>(
|
||||||
|
_webviewBottom.get(),
|
||||||
|
std::move(bottomText),
|
||||||
|
st::paymentsWebviewBottom);
|
||||||
|
const auto height = padding.top()
|
||||||
|
+ label->heightNoMargins()
|
||||||
|
+ padding.bottom();
|
||||||
|
rpl::combine(
|
||||||
|
_webviewBottom->widthValue(),
|
||||||
|
label->widthValue()
|
||||||
|
) | rpl::start_with_next([=](int outerWidth, int width) {
|
||||||
|
label->move((outerWidth - width) / 2, padding.top());
|
||||||
|
}, label->lifetime());
|
||||||
|
label->show();
|
||||||
|
_webviewBottom->resize(_webviewBottom->width(), height);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Panel::createWebview() {
|
||||||
|
auto container = base::make_unique_q<RpWidget>(_widget.get());
|
||||||
|
|
||||||
|
_webviewBottom = std::make_unique<RpWidget>(_widget.get());
|
||||||
|
const auto bottom = _webviewBottom.get();
|
||||||
|
bottom->show();
|
||||||
|
|
||||||
|
bottom->heightValue(
|
||||||
|
) | rpl::start_with_next([=, raw = container.get()](int height) {
|
||||||
|
const auto inner = _widget->innerGeometry();
|
||||||
|
bottom->move(inner.x(), inner.y() + inner.height() - height);
|
||||||
|
raw->resize(inner.width(), inner.height() - height);
|
||||||
|
bottom->resizeToWidth(inner.width());
|
||||||
|
}, bottom->lifetime());
|
||||||
|
container->show();
|
||||||
|
|
||||||
|
_webview = std::make_unique<WebviewWithLifetime>(
|
||||||
|
container.get(),
|
||||||
|
Webview::WindowConfig{
|
||||||
|
.userDataPath = _userDataPath,
|
||||||
|
});
|
||||||
|
const auto raw = &_webview->window;
|
||||||
|
QObject::connect(container.get(), &QObject::destroyed, [=] {
|
||||||
|
if (_webview && &_webview->window == raw) {
|
||||||
|
_webview = nullptr;
|
||||||
|
if (_webviewProgress) {
|
||||||
|
hideWebviewProgress();
|
||||||
|
if (_progress && !_progress->shown) {
|
||||||
|
_progress = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_webviewBottom.get() == bottom) {
|
||||||
|
_webviewBottom = nullptr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!raw->widget()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
container->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](QRect geometry) {
|
||||||
|
raw->widget()->setGeometry(geometry);
|
||||||
|
}, _webview->lifetime);
|
||||||
|
|
||||||
|
raw->setMessageHandler([=](const QJsonDocument &message) {
|
||||||
|
if (!message.isArray()) {
|
||||||
|
LOG(("BotWebView Error: "
|
||||||
|
"Not an array received in buy_callback arguments."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto command = message.array().at(0).toString();
|
||||||
|
if (command == "webview_close") {
|
||||||
|
_close();
|
||||||
|
} else if (command == "webview_send_result_message") {
|
||||||
|
_send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
raw->setNavigationStartHandler([=](const QString &uri) {
|
||||||
|
showWebviewProgress();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
raw->setNavigationDoneHandler([=](bool success) {
|
||||||
|
hideWebviewProgress();
|
||||||
|
});
|
||||||
|
|
||||||
|
raw->init(R"(
|
||||||
|
window.TelegramWebviewProxy = {
|
||||||
|
postEvent: function(eventType, eventData) {
|
||||||
|
if (window.external && window.external.invoke) {
|
||||||
|
window.external.invoke(JSON.stringify([eventType, eventData]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};)");
|
||||||
|
|
||||||
|
_widget->showInner(std::move(container));
|
||||||
|
|
||||||
|
setupProgressGeometry();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::setTitle(rpl::producer<QString> title) {
|
||||||
|
_widget->setTitle(std::move(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showBox(object_ptr<BoxContent> box) {
|
||||||
|
if (const auto widget = _webview ? _webview->window.widget() : nullptr) {
|
||||||
|
const auto hideNow = !widget->isHidden();
|
||||||
|
if (hideNow || _webview->lastHidingBox) {
|
||||||
|
const auto raw = _webview->lastHidingBox = box.data();
|
||||||
|
box->boxClosing(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
const auto widget = _webview
|
||||||
|
? _webview->window.widget()
|
||||||
|
: nullptr;
|
||||||
|
if (widget
|
||||||
|
&& widget->isHidden()
|
||||||
|
&& _webview->lastHidingBox == raw) {
|
||||||
|
widget->show();
|
||||||
|
}
|
||||||
|
}, _webview->lifetime);
|
||||||
|
if (hideNow) {
|
||||||
|
widget->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_widget->showBox(
|
||||||
|
std::move(box),
|
||||||
|
LayerOption::KeepOther,
|
||||||
|
anim::type::normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showToast(const TextWithEntities &text) {
|
||||||
|
_widget->showToast(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showCriticalError(const TextWithEntities &text) {
|
||||||
|
_progress = nullptr;
|
||||||
|
_webviewProgress = false;
|
||||||
|
auto error = base::make_unique_q<PaddingWrap<FlatLabel>>(
|
||||||
|
_widget.get(),
|
||||||
|
object_ptr<FlatLabel>(
|
||||||
|
_widget.get(),
|
||||||
|
rpl::single(text),
|
||||||
|
st::paymentsCriticalError),
|
||||||
|
st::paymentsCriticalErrorPadding);
|
||||||
|
error->entity()->setClickHandlerFilter([=](
|
||||||
|
const ClickHandlerPtr &handler,
|
||||||
|
Qt::MouseButton) {
|
||||||
|
const auto entity = handler->getTextEntity();
|
||||||
|
if (entity.type != EntityType::CustomUrl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
File::OpenUrl(entity.data);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
_widget->showInner(std::move(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showWebviewError(
|
||||||
|
const QString &text,
|
||||||
|
const Webview::Available &information) {
|
||||||
|
using Error = Webview::Available::Error;
|
||||||
|
Expects(information.error != Error::None);
|
||||||
|
|
||||||
|
auto rich = TextWithEntities{ text };
|
||||||
|
rich.append("\n\n");
|
||||||
|
switch (information.error) {
|
||||||
|
case Error::NoWebview2: {
|
||||||
|
const auto command = QString(QChar(TextCommand));
|
||||||
|
const auto text = tr::lng_payments_webview_install_edge(
|
||||||
|
tr::now,
|
||||||
|
lt_link,
|
||||||
|
command);
|
||||||
|
const auto parts = text.split(command);
|
||||||
|
rich.append(parts.value(0))
|
||||||
|
.append(Text::Link(
|
||||||
|
"Microsoft Edge WebView2 Runtime",
|
||||||
|
"https://go.microsoft.com/fwlink/p/?LinkId=2124703"))
|
||||||
|
.append(parts.value(1));
|
||||||
|
} break;
|
||||||
|
case Error::NoGtkOrWebkit2Gtk:
|
||||||
|
rich.append(tr::lng_payments_webview_install_webkit(tr::now));
|
||||||
|
break;
|
||||||
|
case Error::MutterWM:
|
||||||
|
rich.append(tr::lng_payments_webview_switch_mutter(tr::now));
|
||||||
|
break;
|
||||||
|
case Error::Wayland:
|
||||||
|
rich.append(tr::lng_payments_webview_switch_wayland(tr::now));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rich.append(QString::fromStdString(information.details));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
showCriticalError(rich);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::lifetime &Panel::lifetime() {
|
||||||
|
return _widget->lifetime();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Panel> Show(Args &&args) {
|
||||||
|
auto result = std::make_unique<Panel>(
|
||||||
|
args.userDataPath,
|
||||||
|
std::move(args.send),
|
||||||
|
std::move(args.close));
|
||||||
|
result->showWebview(args.url, rpl::single(u"smth"_q));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui::BotWebView
|
77
Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h
Normal file
77
Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
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/object_ptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class BoxContent;
|
||||||
|
class RpWidget;
|
||||||
|
class SeparatePanel;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Webview {
|
||||||
|
struct Available;
|
||||||
|
} // namespace Webview
|
||||||
|
|
||||||
|
namespace Ui::BotWebView {
|
||||||
|
|
||||||
|
class Panel final {
|
||||||
|
public:
|
||||||
|
Panel(const QString &userDataPath, Fn<void()> send, Fn<void()> close);
|
||||||
|
~Panel();
|
||||||
|
|
||||||
|
void requestActivate();
|
||||||
|
void toggleProgress(bool shown);
|
||||||
|
|
||||||
|
bool showWebview(
|
||||||
|
const QString &url,
|
||||||
|
rpl::producer<QString> bottomText);
|
||||||
|
|
||||||
|
void showBox(object_ptr<BoxContent> box);
|
||||||
|
void showToast(const TextWithEntities &text);
|
||||||
|
void showCriticalError(const TextWithEntities &text);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Progress;
|
||||||
|
struct WebviewWithLifetime;
|
||||||
|
|
||||||
|
bool createWebview();
|
||||||
|
void showWebviewProgress();
|
||||||
|
void hideWebviewProgress();
|
||||||
|
void showWebviewError(
|
||||||
|
const QString &text,
|
||||||
|
const Webview::Available &information);
|
||||||
|
void setTitle(rpl::producer<QString> title);
|
||||||
|
|
||||||
|
[[nodiscard]] bool progressWithBackground() const;
|
||||||
|
[[nodiscard]] QRect progressRect() const;
|
||||||
|
void setupProgressGeometry();
|
||||||
|
|
||||||
|
QString _userDataPath;
|
||||||
|
Fn<void()> _send;
|
||||||
|
Fn<void()> _close;
|
||||||
|
std::unique_ptr<SeparatePanel> _widget;
|
||||||
|
std::unique_ptr<WebviewWithLifetime> _webview;
|
||||||
|
std::unique_ptr<RpWidget> _webviewBottom;
|
||||||
|
std::unique_ptr<Progress> _progress;
|
||||||
|
bool _webviewProgress = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
QString url;
|
||||||
|
QString userDataPath;
|
||||||
|
Fn<void()> send;
|
||||||
|
Fn<void()> close;
|
||||||
|
};
|
||||||
|
[[nodiscard]] std::unique_ptr<Panel> Show(Args &&args);
|
||||||
|
|
||||||
|
} // namespace Ui::BotWebView
|
|
@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/text/format_values.h" // Ui::FormatPhone.
|
#include "ui/text/format_values.h" // Ui::FormatPhone.
|
||||||
#include "ui/delayed_activation.h"
|
#include "ui/delayed_activation.h"
|
||||||
|
#include "ui/chat/attach/attach_bot_webview.h"
|
||||||
#include "ui/chat/message_bubble.h"
|
#include "ui/chat/message_bubble.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/chat/chat_theme.h"
|
#include "ui/chat/chat_theme.h"
|
||||||
|
@ -60,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toasts/common_toasts.h"
|
#include "ui/toasts/common_toasts.h"
|
||||||
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
||||||
#include "calls/group/calls_group_call.h"
|
#include "calls/group/calls_group_call.h"
|
||||||
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "ui/boxes/calendar_box.h"
|
#include "ui/boxes/calendar_box.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -72,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_global_privacy.h"
|
#include "api/api_global_privacy.h"
|
||||||
#include "support/support_helper.h"
|
#include "support/support_helper.h"
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
|
#include "storage/storage_domain.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
|
@ -88,8 +91,8 @@ constexpr auto kDayBaseFile = ":/gui/day-custom-base.tdesktop-theme"_cs;
|
||||||
constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
|
constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
|
||||||
|
|
||||||
[[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback(
|
[[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback(
|
||||||
bool dark,
|
bool dark,
|
||||||
std::optional<QColor> accent) {
|
std::optional<QColor> accent) {
|
||||||
return [=](style::palette &palette) {
|
return [=](style::palette &palette) {
|
||||||
using namespace Theme;
|
using namespace Theme;
|
||||||
const auto &embedded = EmbeddedThemes();
|
const auto &embedded = EmbeddedThemes();
|
||||||
|
@ -116,8 +119,8 @@ constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Ui::ChatThemeBubblesData PrepareBubblesData(
|
[[nodiscard]] Ui::ChatThemeBubblesData PrepareBubblesData(
|
||||||
const Data::CloudTheme &theme,
|
const Data::CloudTheme &theme,
|
||||||
Data::CloudThemeType type) {
|
Data::CloudThemeType type) {
|
||||||
const auto i = theme.settings.find(type);
|
const auto i = theme.settings.find(type);
|
||||||
return {
|
return {
|
||||||
.colors = (i != end(theme.settings)
|
.colors = (i != end(theme.settings)
|
||||||
|
@ -129,6 +132,17 @@ constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] UserData *ParseAttachBot(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPAttachMenuBot &bot) {
|
||||||
|
return bot.match([&](const MTPDattachMenuBot &data) {
|
||||||
|
const auto user = session->data().userLoaded(UserId(data.vbot_id()));
|
||||||
|
return (user && user->isBot() && user->botInfo->supportsAttachMenu)
|
||||||
|
? user
|
||||||
|
: nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ActivateWindow(not_null<SessionController*> controller) {
|
void ActivateWindow(not_null<SessionController*> controller) {
|
||||||
|
@ -147,8 +161,8 @@ bool operator!=(const PeerThemeOverride &a, const PeerThemeOverride &b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date)
|
DateClickHandler::DateClickHandler(Dialogs::Key chat, QDate date)
|
||||||
: _chat(chat)
|
: _chat(chat)
|
||||||
, _date(date) {
|
, _date(date) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DateClickHandler::setDate(QDate date) {
|
void DateClickHandler::setDate(QDate date) {
|
||||||
|
@ -163,13 +177,11 @@ void DateClickHandler::onClick(ClickContext context) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionNavigation::SessionNavigation(not_null<Main::Session*> session)
|
SessionNavigation::SessionNavigation(not_null<Main::Session*> session)
|
||||||
: _session(session) {
|
: _session(session)
|
||||||
|
, _api(&_session->mtp()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionNavigation::~SessionNavigation() {
|
SessionNavigation::~SessionNavigation() = default;
|
||||||
_session->api().request(base::take(_showingRepliesRequestId)).cancel();
|
|
||||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
Main::Session &SessionNavigation::session() const {
|
Main::Session &SessionNavigation::session() const {
|
||||||
return *_session;
|
return *_session;
|
||||||
|
@ -193,14 +205,14 @@ void SessionNavigation::showPeerByLink(const PeerByLinkInfo &info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::resolvePhone(
|
void SessionNavigation::resolvePhone(
|
||||||
const QString &phone,
|
const QString &phone,
|
||||||
Fn<void(not_null<PeerData*>)> done) {
|
Fn<void(not_null<PeerData*>)> done) {
|
||||||
if (const auto peer = _session->data().userByPhone(phone)) {
|
if (const auto peer = _session->data().userByPhone(phone)) {
|
||||||
done(peer);
|
done(peer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
_api.request(base::take(_resolveRequestId)).cancel();
|
||||||
_resolveRequestId = _session->api().request(MTPcontacts_ResolvePhone(
|
_resolveRequestId = _api.request(MTPcontacts_ResolvePhone(
|
||||||
MTP_string(phone)
|
MTP_string(phone)
|
||||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
resolveDone(result, done);
|
resolveDone(result, done);
|
||||||
|
@ -216,14 +228,14 @@ void SessionNavigation::resolvePhone(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::resolveUsername(
|
void SessionNavigation::resolveUsername(
|
||||||
const QString &username,
|
const QString &username,
|
||||||
Fn<void(not_null<PeerData*>)> done) {
|
Fn<void(not_null<PeerData*>)> done) {
|
||||||
if (const auto peer = _session->data().peerByUsername(username)) {
|
if (const auto peer = _session->data().peerByUsername(username)) {
|
||||||
done(peer);
|
done(peer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
_api.request(base::take(_resolveRequestId)).cancel();
|
||||||
_resolveRequestId = _session->api().request(MTPcontacts_ResolveUsername(
|
_resolveRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||||
MTP_string(username)
|
MTP_string(username)
|
||||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
resolveDone(result, done);
|
resolveDone(result, done);
|
||||||
|
@ -237,8 +249,8 @@ void SessionNavigation::resolveUsername(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::resolveDone(
|
void SessionNavigation::resolveDone(
|
||||||
const MTPcontacts_ResolvedPeer &result,
|
const MTPcontacts_ResolvedPeer &result,
|
||||||
Fn<void(not_null<PeerData*>)> done) {
|
Fn<void(not_null<PeerData*>)> done) {
|
||||||
_resolveRequestId = 0;
|
_resolveRequestId = 0;
|
||||||
Ui::hideLayer();
|
Ui::hideLayer();
|
||||||
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
||||||
|
@ -251,8 +263,8 @@ void SessionNavigation::resolveDone(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::resolveChannelById(
|
void SessionNavigation::resolveChannelById(
|
||||||
ChannelId channelId,
|
ChannelId channelId,
|
||||||
Fn<void(not_null<ChannelData*>)> done) {
|
Fn<void(not_null<ChannelData*>)> done) {
|
||||||
if (const auto channel = _session->data().channelLoaded(channelId)) {
|
if (const auto channel = _session->data().channelLoaded(channelId)) {
|
||||||
done(channel);
|
done(channel);
|
||||||
return;
|
return;
|
||||||
|
@ -260,10 +272,10 @@ void SessionNavigation::resolveChannelById(
|
||||||
const auto fail = [=] {
|
const auto fail = [=] {
|
||||||
Ui::ShowMultilineToast({
|
Ui::ShowMultilineToast({
|
||||||
.text = { tr::lng_error_post_link_invalid(tr::now) }
|
.text = { tr::lng_error_post_link_invalid(tr::now) }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
_api.request(base::take(_resolveRequestId)).cancel();
|
||||||
_resolveRequestId = _session->api().request(MTPchannels_GetChannels(
|
_resolveRequestId = _api.request(MTPchannels_GetChannels(
|
||||||
MTP_vector<MTPInputChannel>(
|
MTP_vector<MTPInputChannel>(
|
||||||
1,
|
1,
|
||||||
MTP_inputChannel(MTP_long(channelId.bare), MTP_long(0)))
|
MTP_inputChannel(MTP_long(channelId.bare), MTP_long(0)))
|
||||||
|
@ -280,8 +292,8 @@ void SessionNavigation::resolveChannelById(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::showPeerByLinkResolved(
|
void SessionNavigation::showPeerByLinkResolved(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const PeerByLinkInfo &info) {
|
const PeerByLinkInfo &info) {
|
||||||
auto params = SectionShow{
|
auto params = SectionShow{
|
||||||
SectionShow::Way::Forward
|
SectionShow::Way::Forward
|
||||||
};
|
};
|
||||||
|
@ -298,11 +310,11 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||||
const auto bad = [=] {
|
const auto bad = [=] {
|
||||||
Ui::ShowMultilineToast({
|
Ui::ShowMultilineToast({
|
||||||
.text = { tr::lng_group_invite_bad_link(tr::now) }
|
.text = { tr::lng_group_invite_bad_link(tr::now) }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const auto hash = *info.voicechatHash;
|
const auto hash = *info.voicechatHash;
|
||||||
_session->api().request(base::take(_resolveRequestId)).cancel();
|
_api.request(base::take(_resolveRequestId)).cancel();
|
||||||
_resolveRequestId = _session->api().request(
|
_resolveRequestId = _api.request(
|
||||||
MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
|
MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
|
||||||
).done([=](const MTPmessages_ChatFull &result) {
|
).done([=](const MTPmessages_ChatFull &result) {
|
||||||
_session->api().processFullPeer(peer, result);
|
_session->api().processFullPeer(peer, result);
|
||||||
|
@ -322,7 +334,7 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||||
}
|
}
|
||||||
const auto id = call->id();
|
const auto id = call->id();
|
||||||
const auto limit = 5;
|
const auto limit = 5;
|
||||||
_resolveRequestId = _session->api().request(
|
_resolveRequestId = _api.request(
|
||||||
MTPphone_GetGroupCall(call->input(), MTP_int(limit))
|
MTPphone_GetGroupCall(call->input(), MTP_int(limit))
|
||||||
).done([=](const MTPphone_GroupCall &result) {
|
).done([=](const MTPphone_GroupCall &result) {
|
||||||
if (const auto now = peer->groupCall()
|
if (const auto now = peer->groupCall()
|
||||||
|
@ -391,14 +403,14 @@ void SessionNavigation::showPeerByLinkResolved(
|
||||||
}
|
}
|
||||||
crl::on_main(this, [=] {
|
crl::on_main(this, [=] {
|
||||||
showPeerHistory(peer->id, params, msgId);
|
showPeerHistory(peer->id, params, msgId);
|
||||||
showAttachWebview(peer, attachBotUsername);
|
resolveAttachWebview(peer, attachBotUsername);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::showAttachWebview(
|
void SessionNavigation::resolveAttachWebview(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &botUsername) {
|
const QString &botUsername) {
|
||||||
if (!peer->isUser() || botUsername.isEmpty()) {
|
if (!peer->isUser() || botUsername.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -408,28 +420,108 @@ void SessionNavigation::showAttachWebview(
|
||||||
Ui::ShowMultilineToast({
|
Ui::ShowMultilineToast({
|
||||||
// #TODO webview lang
|
// #TODO webview lang
|
||||||
.text = { u"This bot isn't supported in the attach menu."_q }
|
.text = { u"This bot isn't supported in the attach menu."_q }
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
requestAttachWebview(peer, user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// #TODO webview cancel request in destructor
|
void SessionNavigation::requestAttachWebview(
|
||||||
session().api().request(MTPmessages_RequestWebView(
|
not_null<PeerData*> peer,
|
||||||
MTP_flags(0),
|
not_null<UserData*> bot) {
|
||||||
|
_api.request(MTPmessages_RequestWebView(
|
||||||
|
MTP_flags(0),
|
||||||
|
peer->input,
|
||||||
|
bot->inputUser,
|
||||||
|
MTPstring(), // start_param
|
||||||
|
MTPDataJSON() // theme_params
|
||||||
|
)).done([=](const MTPWebViewResult &result) {
|
||||||
|
result.match([&](const MTPDwebViewResultUrl &data) {
|
||||||
|
const auto url = qs(data.vurl());
|
||||||
|
showAttachWebview(peer, bot, data.vquery_id().v, url);
|
||||||
|
}, [&](const MTPDwebViewResultConfirmationRequired &data) {
|
||||||
|
session().data().processUsers(data.vusers());
|
||||||
|
const auto &received = data.vbot();
|
||||||
|
if (const auto bot = ParseAttachBot(&session(), received)) {
|
||||||
|
requestAddToMenu(bot, [=] {
|
||||||
|
requestAttachWebview(peer, bot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
int a = error.code();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionNavigation::showAttachWebview(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
not_null<UserData*> bot,
|
||||||
|
uint64 queryId,
|
||||||
|
const QString &url) {
|
||||||
|
const auto close = crl::guard(this, [=] {
|
||||||
|
_botWebView = nullptr;
|
||||||
|
});
|
||||||
|
const auto send = crl::guard(this, [=] {
|
||||||
|
_api.request(MTPmessages_GetWebViewResult(
|
||||||
peer->input,
|
peer->input,
|
||||||
user->inputUser,
|
bot->inputUser,
|
||||||
MTPstring(), // start_param
|
MTP_long(queryId)
|
||||||
MTPDataJSON() // theme_params
|
)).done([=](const MTPmessages_WebViewResult &result) {
|
||||||
)).done([=](const MTPWebViewResult &result) {
|
result.match([&](const MTPDmessages_webViewResult &data) {
|
||||||
result.match([&](const MTPDwebViewResultUrl &data) {
|
|
||||||
int b = 0;
|
|
||||||
}, [&](const MTPDwebViewResultConfirmationRequired &data) {
|
|
||||||
session().data().processUsers(data.vusers());
|
session().data().processUsers(data.vusers());
|
||||||
int a = 0;
|
auto result = InlineBots::Result::Create(
|
||||||
|
&session(),
|
||||||
|
queryId,
|
||||||
|
data.vresult());
|
||||||
|
_inlineResultConfirmed.fire({
|
||||||
|
.result = result.get(),
|
||||||
|
.bot = bot,
|
||||||
|
.recipientOverride = peer,
|
||||||
|
//.options =
|
||||||
|
});
|
||||||
|
close();
|
||||||
});
|
});
|
||||||
}).fail([=](const MTP::Error &error) {
|
|
||||||
int a = error.code();
|
|
||||||
}).send();
|
}).send();
|
||||||
});
|
});
|
||||||
|
_botWebView = Ui::BotWebView::Show({
|
||||||
|
.url = url,
|
||||||
|
.userDataPath = session().domain().local().webviewDataPath(),
|
||||||
|
.send = send,
|
||||||
|
.close = close,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionNavigation::requestAddToMenu(
|
||||||
|
not_null<UserData*> bot,
|
||||||
|
Fn<void()> callback) {
|
||||||
|
const auto done = [=](Fn<void()> close) {
|
||||||
|
toggleInMenu(bot, true, [=] {
|
||||||
|
callback();
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
show(Ui::MakeConfirmBox({
|
||||||
|
u"Do you want to? "_q + bot->name,
|
||||||
|
done,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionNavigation::toggleInMenu(
|
||||||
|
not_null<UserData*> bot,
|
||||||
|
bool enabled,
|
||||||
|
Fn<void()> callback) {
|
||||||
|
_api.request(MTPmessages_ToggleBotInAttachMenu(
|
||||||
|
bot->inputUser,
|
||||||
|
MTP_bool(enabled)
|
||||||
|
)).done([=](const MTPBool &result) {
|
||||||
|
callback();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SessionNavigation::inlineResultConfirmed() const
|
||||||
|
-> rpl::producer<InlineBots::ResultSelected> {
|
||||||
|
return _inlineResultConfirmed.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionNavigation::showRepliesForMessage(
|
void SessionNavigation::showRepliesForMessage(
|
||||||
|
@ -445,7 +537,7 @@ void SessionNavigation::showRepliesForMessage(
|
||||||
// HistoryView::RepliesWidget right now handles only channels.
|
// HistoryView::RepliesWidget right now handles only channels.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_session->api().request(base::take(_showingRepliesRequestId)).cancel();
|
_api.request(base::take(_showingRepliesRequestId)).cancel();
|
||||||
|
|
||||||
const auto postPeer = history->peer;
|
const auto postPeer = history->peer;
|
||||||
//const auto item = _session->data().message(postPeer, rootId);
|
//const auto item = _session->data().message(postPeer, rootId);
|
||||||
|
@ -461,7 +553,7 @@ void SessionNavigation::showRepliesForMessage(
|
||||||
//}
|
//}
|
||||||
_showingRepliesHistory = history;
|
_showingRepliesHistory = history;
|
||||||
_showingRepliesRootId = rootId;
|
_showingRepliesRootId = rootId;
|
||||||
_showingRepliesRequestId = _session->api().request(
|
_showingRepliesRequestId = _api.request(
|
||||||
MTPmessages_GetDiscussionMessage(
|
MTPmessages_GetDiscussionMessage(
|
||||||
history->peer->input,
|
history->peer->input,
|
||||||
MTP_int(rootId))
|
MTP_int(rootId))
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/layers/layer_widget.h"
|
#include "ui/layers/layer_widget.h"
|
||||||
#include "ui/layers/show.h"
|
#include "ui/layers/show.h"
|
||||||
#include "window/window_adaptive.h"
|
#include "window/window_adaptive.h"
|
||||||
|
#include "mtproto/sender.h"
|
||||||
|
|
||||||
class PhotoData;
|
class PhotoData;
|
||||||
class MainWidget;
|
class MainWidget;
|
||||||
|
@ -60,6 +61,14 @@ struct ChatThemeBackgroundData;
|
||||||
class MessageSendingAnimationController;
|
class MessageSendingAnimationController;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Ui::BotWebView {
|
||||||
|
class Panel;
|
||||||
|
} // namespace Ui::BotWebView
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
|
struct ResultSelected;
|
||||||
|
} // namespace InlineBots
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
struct CloudTheme;
|
struct CloudTheme;
|
||||||
enum class CloudThemeType;
|
enum class CloudThemeType;
|
||||||
|
@ -195,9 +204,12 @@ public:
|
||||||
FullMsgId clickFromMessageId;
|
FullMsgId clickFromMessageId;
|
||||||
};
|
};
|
||||||
void showPeerByLink(const PeerByLinkInfo &info);
|
void showPeerByLink(const PeerByLinkInfo &info);
|
||||||
void showAttachWebview(
|
|
||||||
|
void resolveAttachWebview(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &botUsername);
|
const QString &botUsername);
|
||||||
|
[[nodiscard]] auto inlineResultConfirmed() const
|
||||||
|
-> rpl::producer<InlineBots::ResultSelected>;
|
||||||
|
|
||||||
void showRepliesForMessage(
|
void showRepliesForMessage(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
|
@ -266,14 +278,34 @@ private:
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const PeerByLinkInfo &info);
|
const PeerByLinkInfo &info);
|
||||||
|
|
||||||
|
void requestAttachWebview(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
not_null<UserData*> bot);
|
||||||
|
void requestAddToMenu(not_null<UserData*> bot, Fn<void()> callback);
|
||||||
|
void showAttachWebview(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
not_null<UserData*> bot,
|
||||||
|
uint64 queryId,
|
||||||
|
const QString &url);
|
||||||
|
|
||||||
|
void toggleInMenu(
|
||||||
|
not_null<UserData*> bot,
|
||||||
|
bool enabled,
|
||||||
|
Fn<void()> callback);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
mtpRequestId _resolveRequestId = 0;
|
mtpRequestId _resolveRequestId = 0;
|
||||||
|
|
||||||
History *_showingRepliesHistory = nullptr;
|
History *_showingRepliesHistory = nullptr;
|
||||||
MsgId _showingRepliesRootId = 0;
|
MsgId _showingRepliesRootId = 0;
|
||||||
mtpRequestId _showingRepliesRequestId = 0;
|
mtpRequestId _showingRepliesRequestId = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::BotWebView::Panel> _botWebView;
|
||||||
|
rpl::event_stream<InlineBots::ResultSelected> _inlineResultConfirmed;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SessionController : public SessionNavigation {
|
class SessionController : public SessionNavigation {
|
||||||
|
|
|
@ -157,6 +157,8 @@ PRIVATE
|
||||||
ui/chat/attach/attach_album_preview.h
|
ui/chat/attach/attach_album_preview.h
|
||||||
ui/chat/attach/attach_album_thumbnail.cpp
|
ui/chat/attach/attach_album_thumbnail.cpp
|
||||||
ui/chat/attach/attach_album_thumbnail.h
|
ui/chat/attach/attach_album_thumbnail.h
|
||||||
|
ui/chat/attach/attach_bot_webview.cpp
|
||||||
|
ui/chat/attach/attach_bot_webview.h
|
||||||
ui/chat/attach/attach_controls.cpp
|
ui/chat/attach/attach_controls.cpp
|
||||||
ui/chat/attach/attach_controls.h
|
ui/chat/attach/attach_controls.h
|
||||||
ui/chat/attach/attach_extensions.cpp
|
ui/chat/attach/attach_extensions.cpp
|
||||||
|
|
Loading…
Add table
Reference in a new issue