diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 09543c51e..e2af7d4e4 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1722,6 +1722,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_sure_add_text_group" = "Are you sure you want to add this bot as an admin in the group {group}?"; "lng_bot_sure_add_text_channel" = "Are you sure you want to add this bot as an admin in the channel {group}?"; "lng_bot_sure_add" = "Add as admin"; +"lng_bot_no_webview" = "Unfortunately, you can't open such menu with current system configuration."; +"lng_bot_remove_from_menu" = "Remove from menu"; +"lng_bot_remove_from_menu_done" = "Bot removed from the menu."; +"lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachments menu so you can access it from any chat."; +"lng_bot_add_to_menu_done" = "Bot added to the menu."; +"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_typing" = "typing"; "lng_user_typing" = "{user} is typing"; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0b7b9c10f..9e745de8e 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -489,9 +489,18 @@ HistoryWidget::HistoryWidget( _attachBotsMenu = nullptr; return; } else if (!_attachBotsMenu) { + const auto forceShown = [=](bool shown) { + if (shown) { + _attachBotsMenu->setAutoHiding(false); + } else { + _attachBotsMenu->hideAnimated(); + _attachBotsMenu->setAutoHiding(true); + } + }; _attachBotsMenu = InlineBots::MakeAttachBotsMenu( this, - controller); + controller, + forceShown); _attachBotsMenu->setOrigin( Ui::PanelAnimation::Origin::BottomLeft); if (_history && _history->peer->isUser()) { diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 9cafead1d..4b18e9725 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_file_origin.h" #include "data/data_document.h" +#include "data/data_document_media.h" #include "data/data_session.h" #include "main/main_session.h" #include "main/main_domain.h" @@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toasts/common_toasts.h" #include "ui/chat/attach/attach_bot_webview.h" #include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_item_base.h" #include "ui/effects/ripple_animation.h" #include "window/themes/window_theme.h" @@ -32,6 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "styles/style_menu_icons.h" +#include + namespace InlineBots { namespace { @@ -81,23 +85,32 @@ public: bool isEnabled() const override; not_null action() const override; + [[nodiscard]] rpl::producer forceShown() const; + void handleKeyPress(not_null e) override; -protected: +private: + void contextMenuEvent(QContextMenuEvent *e) override; + QPoint prepareRippleStartPosition() const override; QImage prepareRippleMask() const override; int contentHeight() const override; -private: void prepare(); + void validateIcon(); void paint(Painter &p); const not_null _dummyAction; const style::Menu &_st; const AttachWebViewBot _bot; + base::unique_qptr _menu; + rpl::event_stream _forceShown; + Ui::Text::String _text; + QImage _mask; + QImage _icon; int _textWidth = 0; const int _height; @@ -115,7 +128,7 @@ BotAction::BotAction( , _height(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom()) { - setAcceptBoth(true); + setAcceptBoth(false); initResizeHook(parent->sizeValue()); setClickedCallback(std::move(callback)); @@ -125,11 +138,48 @@ BotAction::BotAction( paint(p); }, lifetime()); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _icon = QImage(); + update(); + }, lifetime()); + enableMouseSelecting(); prepare(); } +void BotAction::validateIcon() { + if (_mask.isNull()) { + if (!_bot.media->loaded()) { + return; + } + auto icon = QSvgRenderer(_bot.media->bytes()); + if (!icon.isValid()) { + _mask = QImage( + QSize(1, 1) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + _mask.fill(Qt::transparent); + } else { + const auto size = style::ConvertScale(icon.defaultSize()); + _mask = QImage( + size * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + _mask.fill(Qt::transparent); + { + auto p = QPainter(&_mask); + icon.render(&p, QRect(QPoint(), size)); + } + _mask = Images::Colored(std::move(_mask), QColor(255, 255, 255)); + } + } + if (_icon.isNull()) { + _icon = style::colorizeImage(_mask, st::menuIconColor); + } +} + void BotAction::paint(Painter &p) { + validateIcon(); + const auto selected = isSelected(); if (selected && _st.itemBgOver->c.alpha() < 255) { p.fillRect(0, 0, width(), _height, _st.itemBg); @@ -139,14 +189,9 @@ void BotAction::paint(Painter &p) { paintRipple(p, 0, 0); } - const auto normalHeight = _st.itemPadding.top() - + _st.itemStyle.font->height - + _st.itemPadding.bottom(); - const auto deltaHeight = _height - normalHeight; - st::menuIconDelete.paint( - p, - _st.itemIconPosition + QPoint(0, deltaHeight / 2), - width()); + if (!_icon.isNull()) { + p.drawImage(_st.itemIconPosition, _icon); + } p.setPen(selected ? _st.itemFgOver : _st.itemFg); _text.drawLeftElided( @@ -180,6 +225,24 @@ not_null BotAction::action() const { return _dummyAction; } +void BotAction::contextMenuEvent(QContextMenuEvent *e) { + _menu = nullptr; + _menu = base::make_unique_q( + this, + st::popupMenuWithIcons); + _menu->addAction(tr::lng_bot_remove_from_menu(tr::now), [=] { + _bot.user->session().attachWebView().removeFromMenu(_bot.user); + }, &st::menuIconDelete); + + QObject::connect(_menu, &QObject::destroyed, [=] { + _forceShown.fire(false); + }); + + _forceShown.fire(true); + _menu->popup(e->globalPos()); + e->accept(); +} + QPoint BotAction::prepareRippleStartPosition() const { return mapFromGlobal(QCursor::pos()); } @@ -192,6 +255,10 @@ int BotAction::contentHeight() const { return _height; } +rpl::producer BotAction::forceShown() const { + return _forceShown.events(); +} + void BotAction::handleKeyPress(not_null e) { if (!isSelected()) { return; @@ -348,7 +415,8 @@ void AttachWebView::requestAddToMenu(not_null bot) { } else { requestBots(); Ui::ShowMultilineToast({ - .text = { u"Bot is already added."_q }, + .text = { + tr::lng_bot_menu_already_added(tr::now) }, }); } } @@ -358,13 +426,17 @@ void AttachWebView::requestAddToMenu(not_null bot) { _addToMenuId = 0; _addToMenuBot = nullptr; Ui::ShowMultilineToast({ - .text = { u"Bot cannot be added to the menu."_q }, - }); + .text = { tr::lng_bot_menu_not_supported(tr::now) }, + }); }).send(); } void AttachWebView::removeFromMenu(not_null bot) { - toggleInMenu(bot, false, nullptr); + toggleInMenu(bot, false, [=] { + Ui::ShowMultilineToast({ + .text = { tr::lng_bot_remove_from_menu_done(tr::now) }, + }); + }); } void AttachWebView::resolve() { @@ -378,8 +450,7 @@ void AttachWebView::requestByUsername() { _bot = bot->asUser(); if (!_bot || !_bot->isBot() || !_bot->botInfo->supportsAttachMenu) { Ui::ShowMultilineToast({ - // #TODO webview lang - .text = { u"This bot isn't supported in the attach menu."_q } + .text = { tr::lng_bot_menu_not_supported(tr::now) } }); return; } @@ -529,15 +600,18 @@ void AttachWebView::confirmAddToMenu( if (callback) { callback(); } - close(); + Ui::ShowMultilineToast({ + .text = { tr::lng_bot_add_to_menu_done(tr::now) }, + }); }); + close(); }; const auto active = Core::App().activeWindow(); if (!active) { return; } _confirmAddBox = active->show(Ui::MakeConfirmBox({ - u"Do you want to? "_q + bot.name, + tr::lng_bot_add_to_menu(tr::now, lt_bot, bot.name), done, })); } @@ -562,7 +636,8 @@ void AttachWebView::toggleInMenu( std::unique_ptr MakeAttachBotsMenu( not_null parent, - not_null controller) { + not_null controller, + Fn forceShown) { auto result = std::make_unique( parent, st::dropdownMenuWithIcons); @@ -571,12 +646,22 @@ std::unique_ptr MakeAttachBotsMenu( const auto refresh = [=] { raw->clearActions(); for (const auto &bot : bots->attachBots()) { - raw->addAction(base::make_unique_q(raw, bot, raw->menu()->st(), [=] { + const auto callback = [=] { const auto active = controller->activeChatCurrent(); if (const auto history = active.history()) { bots->request(history->peer, bot.user); } - })); + }; + auto action = base::make_unique_q( + raw, + raw->menu()->st(), + bot, + callback); + action->forceShown( + ) | rpl::start_with_next([=](bool shown) { + forceShown(shown); + }, action->lifetime()); + raw->addAction(std::move(action)); } }; refresh(); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index add39f803..59456b1b1 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -85,7 +85,7 @@ private: void toggleInMenu( not_null bot, bool enabled, - Fn callback); + Fn callback = nullptr); void show( uint64 queryId, @@ -122,6 +122,7 @@ private: [[nodiscard]] std::unique_ptr MakeAttachBotsMenu( not_null parent, - not_null controller); + not_null controller, + Fn forceShown); } // namespace InlineBots diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 6415f1964..daee5a84c 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -482,7 +482,7 @@ std::unique_ptr Show(Args &&args) { const auto available = Webview::Availability(); if (available.error != Webview::Available::Error::None) { result->showWebviewError( - tr::lng_payments_webview_no_card(tr::now), + tr::lng_bot_no_webview(tr::now), available); } else { result->showCriticalError({