Support custom popups in attach bots.

This commit is contained in:
John Preston 2022-07-07 08:39:33 +04:00
parent e954871cb9
commit c1e7e63677
6 changed files with 169 additions and 37 deletions

View File

@ -1970,6 +1970,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_menu_not_supported" = "This bot isn't supported in the attach menu.";
"lng_bot_menu_already_added" = "This bot is already added in your attach menu.";
"lng_bot_menu_button" = "Menu";
"lng_bot_close_warning_title" = "Warning";
"lng_bot_close_warning" = "Changes that you made may not be saved.";
"lng_bot_close_warning_sure" = "Close anyway";
"lng_typing" = "typing";
"lng_user_typing" = "{user} is typing";

View File

@ -333,10 +333,26 @@ QString UiIntegration::phraseButtonOk() {
return tr::lng_box_ok(tr::now);
}
QString UiIntegration::phraseButtonClose() {
return tr::lng_close(tr::now);
}
QString UiIntegration::phraseButtonCancel() {
return tr::lng_cancel(tr::now);
}
QString UiIntegration::phrasePanelCloseWarning() {
return tr::lng_bot_close_warning_title(tr::now);
}
QString UiIntegration::phrasePanelCloseUnsaved() {
return tr::lng_bot_close_warning(tr::now);
}
QString UiIntegration::phrasePanelCloseAnyway() {
return tr::lng_bot_close_warning_sure(tr::now);
}
bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath());
}

View File

@ -75,7 +75,11 @@ public:
QString phraseFormattingMonospace() override;
QString phraseFormattingSpoiler() override;
QString phraseButtonOk() override;
QString phraseButtonClose() override;
QString phraseButtonCancel() override;
QString phrasePanelCloseWarning() override;
QString phrasePanelCloseUnsaved() override;
QString phrasePanelCloseAnyway() override;
};

View File

@ -17,10 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/integration.h"
#include "lang/lang_keys.h"
#include "webview/webview_embed.h"
#include "webview/webview_dialog.h"
#include "webview/webview_interface.h"
#include "base/debug_log.h"
#include "base/invoke_queued.h"
#include "styles/style_payments.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
@ -40,11 +43,14 @@ constexpr auto kLightnessThreshold = 128;
constexpr auto kLightnessDelta = 32;
[[nodiscard]] QJsonObject ParseMethodArgs(const QString &json) {
if (json.isEmpty()) {
return {};
}
auto error = QJsonParseError();
const auto dictionary = QJsonDocument::fromJson(json.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
LOG(("BotWebView Error: Could not parse \"%1\".").arg(json));
return QJsonObject();
return {};
}
return dictionary.object();
}
@ -349,7 +355,13 @@ Panel::Panel(
_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
_widget->closeRequests(
) | rpl::start_with_next(_close, _widget->lifetime());
) | rpl::start_with_next([=] {
if (_closeNeedConfirmation) {
scheduleCloseWithConfirmation();
} else {
_close();
}
}, _widget->lifetime());
_widget->closeEvents(
) | rpl::filter([=] {
@ -625,22 +637,27 @@ bool Panel::createWebview() {
}
const auto list = message.array();
const auto command = list.at(0).toString();
const auto arguments = ParseMethodArgs(list.at(1).toString());
if (command == "web_app_close") {
_close();
} else if (command == "web_app_data_send") {
sendDataMessage(list.at(1));
sendDataMessage(arguments);
} else if (command == "web_app_setup_main_button") {
processMainButtonMessage(list.at(1));
processMainButtonMessage(arguments);
} else if (command == "web_app_setup_back_button") {
processBackButtonMessage(list.at(1));
processBackButtonMessage(arguments);
} else if (command == "web_app_request_theme") {
_themeUpdateForced.fire({});
} else if (command == "web_app_open_tg_link") {
openTgLink(list.at(1).toString());
openTgLink(arguments);
} else if (command == "web_app_open_link") {
openExternalLink(list.at(1).toString());
openExternalLink(arguments);
} else if (command == "web_app_open_invoice") {
openInvoice(list.at(1).toString());
openInvoice(arguments);
} else if (command == "web_app_open_popup") {
openPopup(arguments);
} else if (command == "web_app_setup_closing_behavior") {
setupClosingBehaviour(arguments);
}
});
@ -675,41 +692,35 @@ void Panel::setTitle(rpl::producer<QString> title) {
_widget->setTitle(std::move(title));
}
void Panel::sendDataMessage(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::sendDataMessage(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
}
const auto data = args["data"].toString();
if (data.isEmpty()) {
LOG(("BotWebView Error: Bad data \"%1\".").arg(json));
LOG(("BotWebView Error: Bad 'data' in sendDataMessage."));
_close();
return;
}
_sendData(data.toUtf8());
}
void Panel::openTgLink(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::openTgLink(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
}
const auto path = args["path_full"].toString();
if (path.isEmpty()) {
LOG(("BotWebView Error: Bad tg link \"%1\".").arg(json));
LOG(("BotWebView Error: Bad 'path_full' in openTgLink."));
_close();
return;
}
_handleLocalUri("https://t.me" + path);
}
void Panel::openExternalLink(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::openExternalLink(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
@ -718,7 +729,7 @@ void Panel::openExternalLink(const QJsonValue &value) {
const auto lower = url.toLower();
if (url.isEmpty()
|| (!lower.startsWith("http://") && !lower.startsWith("https://"))) {
LOG(("BotWebView Error: Bad external link \"%1\".").arg(json));
LOG(("BotWebView Error: Bad 'url' in openExternalLink."));
_close();
return;
}
@ -733,25 +744,119 @@ void Panel::openExternalLink(const QJsonValue &value) {
}
}
void Panel::openInvoice(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::openInvoice(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
}
const auto slug = args["slug"].toString();
if (slug.isEmpty()) {
LOG(("BotWebView Error: Bad invoice \"%1\".").arg(json));
LOG(("BotWebView Error: Bad 'slug' in openInvoice."));
_close();
return;
}
_handleInvoice(slug);
}
void Panel::processMainButtonMessage(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::openPopup(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
}
using Button = Webview::PopupArgs::Button;
using Type = Button::Type;
const auto message = args["message"].toString();
const auto types = base::flat_map<QString, Button::Type>{
{ "default", Type::Default },
{ "ok", Type::Ok },
{ "close", Type::Close },
{ "cancel", Type::Cancel },
{ "destructive", Type::Destructive },
};
auto buttons = std::vector<Webview::PopupArgs::Button>();
for (const auto &button : args["buttons"].toArray()) {
const auto fields = button.toObject();
const auto i = types.find(fields["type"].toString());
if (i == end(types)) {
LOG(("BotWebView Error: Bad 'type' in openPopup buttons."));
_close();
return;
}
buttons.push_back({
.id = fields["id"].toString(),
.text = fields["text"].toString(),
.type = i->second,
});
}
if (message.isEmpty()) {
LOG(("BotWebView Error: Bad 'message' in openPopup."));
_close();
return;
} else if (buttons.empty()) {
LOG(("BotWebView Error: Bad 'buttons' in openPopup."));
_close();
return;
}
const auto widget = _webview->window.widget();
const auto weak = base::make_weak(this);
const auto result = Webview::ShowBlockingPopup({
.parent = widget ? widget->window() : nullptr,
.title = args["title"].toString(),
.text = message,
.buttons = std::move(buttons),
});
if (weak) {
const auto safe = [&] {
return QJsonDocument(
QJsonArray{ { QJsonValue(*result.id) } }
).toJson(QJsonDocument::Compact) + "[0]";
};
postEvent(
"popup_closed",
result.id ? ("\"button_id\": " + safe()) : QString());
}
}
void Panel::scheduleCloseWithConfirmation() {
if (!_closeWithConfirmationScheduled) {
_closeWithConfirmationScheduled = true;
InvokeQueued(_widget.get(), [=] { closeWithConfirmation(); });
}
}
void Panel::closeWithConfirmation() {
using Button = Webview::PopupArgs::Button;
const auto widget = _webview->window.widget();
const auto weak = base::make_weak(this);
const auto integration = &Ui::Integration::Instance();
const auto result = Webview::ShowBlockingPopup({
.parent = widget ? widget->window() : nullptr,
.title = integration->phrasePanelCloseWarning(),
.text = integration->phrasePanelCloseUnsaved(),
.buttons = {
{
.id = "close",
.text = integration->phrasePanelCloseAnyway(),
.type = Button::Type::Destructive,
},
{ .id = "cancel", .type = Button::Type::Cancel },
},
.ignoreFloodCheck = true,
});
if (!weak) {
return;
} else if (result.id != "cancel") {
_close();
} else {
_closeWithConfirmationScheduled = false;
}
}
void Panel::setupClosingBehaviour(const QJsonObject &args) {
_closeNeedConfirmation = args["need_confirmation"].toBool();
}
void Panel::processMainButtonMessage(const QJsonObject &args) {
if (args.isEmpty()) {
_close();
return;
@ -795,9 +900,7 @@ void Panel::processMainButtonMessage(const QJsonValue &value) {
});
}
void Panel::processBackButtonMessage(const QJsonValue &value) {
const auto json = value.toString();
const auto args = ParseMethodArgs(json);
void Panel::processBackButtonMessage(const QJsonObject &args) {
_widget->setBackAllowed(args["is_visible"].toBool());
}

View File

@ -85,13 +85,17 @@ private:
void showWebviewProgress();
void hideWebviewProgress();
void setTitle(rpl::producer<QString> title);
void sendDataMessage(const QJsonValue &value);
void processMainButtonMessage(const QJsonValue &value);
void processBackButtonMessage(const QJsonValue &value);
void openTgLink(const QJsonValue &value);
void openExternalLink(const QJsonValue &value);
void openInvoice(const QJsonValue &value);
void sendDataMessage(const QJsonObject &args);
void processMainButtonMessage(const QJsonObject &args);
void processBackButtonMessage(const QJsonObject &args);
void openTgLink(const QJsonObject &args);
void openExternalLink(const QJsonObject &args);
void openInvoice(const QJsonObject &args);
void openPopup(const QJsonObject &args);
void setupClosingBehaviour(const QJsonObject &args);
void createMainButton();
void scheduleCloseWithConfirmation();
void closeWithConfirmation();
void postEvent(const QString &event, const QString &data = {});
@ -104,6 +108,7 @@ private:
Fn<void(QString)> _handleInvoice;
Fn<void(QByteArray)> _sendData;
Fn<void()> _close;
bool _closeNeedConfirmation = false;
MenuButtons _menuButtons = {};
Fn<void(MenuButton)> _handleMenuButton;
std::unique_ptr<SeparatePanel> _widget;
@ -119,6 +124,7 @@ private:
bool _webviewProgress = false;
bool _themeUpdateScheduled = false;
bool _hiddenForPayment = false;
bool _closeWithConfirmationScheduled = false;
};

@ -1 +1 @@
Subproject commit d7318d849ef89873a1b4f78f23e471555061921b
Subproject commit a7074136b4d8ba17b623cf673afc054f3aa3145b