diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b272f4b955..ac31a33a9f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index 067f994532..31733fc73d 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -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()); } diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 7e61e10606..5f96646826 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -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; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index d7bf74ff46..0823557dfb 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -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 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{ + { "default", Type::Default }, + { "ok", Type::Ok }, + { "close", Type::Close }, + { "cancel", Type::Cancel }, + { "destructive", Type::Destructive }, + }; + auto buttons = std::vector(); + 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()); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 8726b6f3cb..fb53154dfc 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -85,13 +85,17 @@ private: void showWebviewProgress(); void hideWebviewProgress(); void setTitle(rpl::producer 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 _handleInvoice; Fn _sendData; Fn _close; + bool _closeNeedConfirmation = false; MenuButtons _menuButtons = {}; Fn _handleMenuButton; std::unique_ptr _widget; @@ -119,6 +124,7 @@ private: bool _webviewProgress = false; bool _themeUpdateScheduled = false; bool _hiddenForPayment = false; + bool _closeWithConfirmationScheduled = false; }; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index d7318d849e..a7074136b4 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit d7318d849ef89873a1b4f78f23e471555061921b +Subproject commit a7074136b4d8ba17b623cf673afc054f3aa3145b