diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 9b0590909..957dfd863 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -362,6 +362,7 @@ bool ResolveUsernameOrPhone( .startToken = startToken, .startAdminRights = adminRights, .attachBotUsername = params.value(u"attach"_q), + .attachBotToggle = params.contains(u"setattach"_q), .voicechatHash = (params.contains(u"livestream"_q) ? std::make_optional(params.value(u"livestream"_q)) : params.contains(u"videochat"_q) diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 56bb69d9c..aae2bd788 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -677,9 +677,14 @@ Storage::Cache::Key DocumentData::bigFileBaseCacheKey() const { : Storage::Cache::Key(); } +void DocumentData::forceToCache(bool force) { + _flags |= Flag::ForceToCache; +} + bool DocumentData::saveToCache() const { return (size < Storage::kMaxFileInMemory) && ((type == StickerDocument) + || (_flags & Flag::ForceToCache) || isAnimation() || isVoiceMessage() || isWallPaper() diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index c82ac71ef..9b648527c 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -133,6 +133,7 @@ public: bool saveFromDataSilent(); [[nodiscard]] QString filepath(bool check = false) const; + void forceToCache(bool force); [[nodiscard]] bool saveToCache() const; [[nodiscard]] Image *getReplyPreview( @@ -268,15 +269,16 @@ public: std::unique_ptr uploadingData; private: - enum class Flag : uchar { - StreamingMaybeYes = 0x01, - StreamingMaybeNo = 0x02, - StreamingPlaybackFailed = 0x04, - ImageType = 0x08, - DownloadCancelled = 0x10, - LoadedInMediaCache = 0x20, - HasAttachedStickers = 0x40, - InlineThumbnailIsPath = 0x80, + enum class Flag : ushort { + StreamingMaybeYes = 0x001, + StreamingMaybeNo = 0x002, + StreamingPlaybackFailed = 0x004, + ImageType = 0x008, + DownloadCancelled = 0x010, + LoadedInMediaCache = 0x020, + HasAttachedStickers = 0x040, + InlineThumbnailIsPath = 0x080, + ForceToCache = 0x100, }; using Flags = base::flags; friend constexpr bool is_flag_type(Flag) { return true; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index abae3a057..0b7b9c10f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -497,6 +497,10 @@ HistoryWidget::HistoryWidget( if (_history && _history->peer->isUser()) { _attachToggle->installEventFilter(_attachBotsMenu.get()); } + _attachBotsMenu->heightValue( + ) | rpl::start_with_next([=] { + moveFieldControls(); + }, _attachBotsMenu->lifetime()); } }, lifetime()); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index ccdeda14d..9cafead1d 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -8,15 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/bot_attach_web_view.h" #include "data/data_user.h" +#include "data/data_file_origin.h" +#include "data/data_document.h" #include "data/data_session.h" #include "main/main_session.h" #include "main/main_domain.h" #include "storage/storage_domain.h" #include "info/profile/info_profile_values.h" #include "ui/boxes/confirm_box.h" -#include "ui/widgets/dropdown_menu.h" #include "ui/toasts/common_toasts.h" #include "ui/chat/attach/attach_bot_webview.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "ui/effects/ripple_animation.h" #include "window/themes/window_theme.h" #include "window/window_controller.h" #include "window/window_session_controller.h" @@ -33,15 +37,32 @@ namespace { constexpr auto kProlongTimeout = 60 * crl::time(1000); -[[nodiscard]] UserData *ParseAttachBot( +struct ParsedBot { + UserData *bot = nullptr; + bool inactive = false; +}; + +[[nodiscard]] std::optional ParseAttachBot( not_null session, const MTPAttachMenuBot &bot) { - return bot.match([&](const MTPDattachMenuBot &data) { + auto result = bot.match([&](const MTPDattachMenuBot &data) { const auto user = session->data().userLoaded(UserId(data.vbot_id())); - return (user && user->isBot() && user->botInfo->supportsAttachMenu) - ? user - : nullptr; + const auto good = user + && user->isBot() + && user->botInfo->supportsAttachMenu; + return good + ? AttachWebViewBot{ + .user = user, + .icon = session->data().processDocument( + data.vattach_menu_icon()), + .name = qs(data.vattach_menu_name()), + .inactive = data.is_inactive(), + } : std::optional(); }); + if (result) { + result->icon->forceToCache(true); + } + return result; } [[nodiscard]] base::flat_set> &ActiveWebViews() { @@ -49,6 +70,138 @@ constexpr auto kProlongTimeout = 60 * crl::time(1000); return result; } +class BotAction final : public Ui::Menu::ItemBase { +public: + BotAction( + not_null parent, + const style::Menu &st, + const AttachWebViewBot &bot, + Fn callback); + + bool isEnabled() const override; + not_null action() const override; + + void handleKeyPress(not_null e) override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void prepare(); + void paint(Painter &p); + + const not_null _dummyAction; + const style::Menu &_st; + const AttachWebViewBot _bot; + + Ui::Text::String _text; + int _textWidth = 0; + const int _height; + +}; + +BotAction::BotAction( + not_null parent, + const style::Menu &st, + const AttachWebViewBot &bot, + Fn callback) +: ItemBase(parent, st) +, _dummyAction(new QAction(parent)) +, _st(st) +, _bot(bot) +, _height(_st.itemPadding.top() + + _st.itemStyle.font->height + + _st.itemPadding.bottom()) { + setAcceptBoth(true); + initResizeHook(parent->sizeValue()); + setClickedCallback(std::move(callback)); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + enableMouseSelecting(); + prepare(); +} + +void BotAction::paint(Painter &p) { + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + 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()); + + p.setPen(selected ? _st.itemFgOver : _st.itemFg); + _text.drawLeftElided( + p, + _st.itemPadding.left(), + _st.itemPadding.top(), + _textWidth, + width()); +} + +void BotAction::prepare() { + _text.setMarkedText(_st.itemStyle, { _bot.name }); + const auto textWidth = _text.maxWidth(); + const auto &padding = _st.itemPadding; + + const auto goodWidth = padding.left() + + textWidth + + padding.right(); + + const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); + _textWidth = w - (goodWidth - textWidth); + setMinWidth(w); + update(); +} + +bool BotAction::isEnabled() const { + return true; +} + +not_null BotAction::action() const { + return _dummyAction; +} + +QPoint BotAction::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage BotAction::prepareRippleMask() const { + return Ui::RippleAnimation::rectMask(size()); +} + +int BotAction::contentHeight() const { + return _height; +} + +void BotAction::handleKeyPress(not_null e) { + if (!isSelected()) { + return; + } + const auto key = e->key(); + if (key == Qt::Key_Enter || key == Qt::Key_Return) { + setClicked(Ui::Menu::TriggeredSource::Keyboard); + } +} + } // namespace AttachWebView::AttachWebView(not_null session) @@ -115,11 +268,11 @@ void AttachWebView::request(const WebViewButton &button) { _session->data().processUsers(data.vusers()); const auto &received = data.vbot(); if (const auto bot = ParseAttachBot(_session, received)) { - if (_bot != bot) { + if (_bot != bot->user) { cancel(); return; } - requestAddToMenu([=] { + confirmAddToMenu(*bot, [=] { request(button); }); } else { @@ -156,15 +309,13 @@ void AttachWebView::requestBots() { _attachBots.clear(); _attachBots.reserve(data.vbots().v.size()); for (const auto &bot : data.vbots().v) { - bot.match([&](const MTPDattachMenuBot &data) { - if (data.is_inactive()) { - return; + if (auto parsed = ParseAttachBot(_session, bot)) { + if (!parsed->inactive) { + parsed->media = parsed->icon->createMediaView(); + parsed->icon->save(Data::FileOrigin(), {}); + _attachBots.push_back(std::move(*parsed)); } - _attachBots.push_back({ - .user = _session->data().user(data.vbot_id()), - .name = qs(data.vattach_menu_name()), - }); - }); + } } _attachBotsUpdates.fire({}); }); @@ -173,6 +324,49 @@ void AttachWebView::requestBots() { }).send(); } +void AttachWebView::requestAddToMenu(not_null bot) { + if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { + return; + } else if (_addToMenuId) { + if (_addToMenuBot == bot) { + return; + } + _session->api().request(base::take(_addToMenuId)).cancel(); + } + _addToMenuBot = bot; + _addToMenuId = _session->api().request(MTPmessages_GetAttachMenuBot( + bot->inputUser + )).done([=](const MTPAttachMenuBotsBot &result) { + _addToMenuId = 0; + const auto requested = base::take(_addToMenuBot); + result.match([&](const MTPDattachMenuBotsBot &data) { + _session->data().processUsers(data.vusers()); + if (const auto bot = ParseAttachBot(_session, data.vbot())) { + if (requested == bot->user) { + if (bot->inactive) { + confirmAddToMenu(*bot); + } else { + requestBots(); + Ui::ShowMultilineToast({ + .text = { u"Bot is already added."_q }, + }); + } + } + } + }); + }).fail([=] { + _addToMenuId = 0; + _addToMenuBot = nullptr; + Ui::ShowMultilineToast({ + .text = { u"Bot cannot be added to the menu."_q }, + }); + }).send(); +} + +void AttachWebView::removeFromMenu(not_null bot) { + toggleInMenu(bot, false, nullptr); +} + void AttachWebView::resolve() { if (!_bot) { requestByUsername(); @@ -327,12 +521,14 @@ void AttachWebView::started(uint64 queryId) { }, _panel->lifetime()); } -void AttachWebView::requestAddToMenu(Fn callback) { - Expects(_bot != nullptr); - +void AttachWebView::confirmAddToMenu( + AttachWebViewBot bot, + Fn callback) { const auto done = [=](Fn close) { - toggleInMenu( true, [=] { - callback(); + toggleInMenu(bot.user, true, [=] { + if (callback) { + callback(); + } close(); }); }; @@ -341,21 +537,24 @@ void AttachWebView::requestAddToMenu(Fn callback) { return; } _confirmAddBox = active->show(Ui::MakeConfirmBox({ - u"Do you want to? "_q + _bot->name, + u"Do you want to? "_q + bot.name, done, })); } -void AttachWebView::toggleInMenu(bool enabled, Fn callback) { - Expects(_bot != nullptr); - - _requestId = _session->api().request(MTPmessages_ToggleBotInAttachMenu( - _bot->inputUser, +void AttachWebView::toggleInMenu( + not_null bot, + bool enabled, + Fn callback) { + _session->api().request(MTPmessages_ToggleBotInAttachMenu( + bot->inputUser, MTP_bool(enabled) )).done([=] { _requestId = 0; requestBots(); - callback(); + if (callback) { + callback(); + } }).fail([=] { cancel(); }).send(); @@ -372,12 +571,12 @@ std::unique_ptr MakeAttachBotsMenu( const auto refresh = [=] { raw->clearActions(); for (const auto &bot : bots->attachBots()) { - raw->addAction(bot.name, [=, bot = bot.user]{ + raw->addAction(base::make_unique_q(raw, bot, raw->menu()->st(), [=] { const auto active = controller->activeChatCurrent(); if (const auto history = active.history()) { - bots->request(history->peer, bot); + bots->request(history->peer, bot.user); } - }); + })); } }; refresh(); diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 914360714..add39f803 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -27,11 +27,18 @@ namespace Window { class SessionController; } // namespace Window +namespace Data { +class DocumentMedia; +} // namespace Data + namespace InlineBots { struct AttachWebViewBot { not_null user; + not_null icon; + std::shared_ptr media; QString name; + bool inactive = false; }; class AttachWebView final : public base::has_weak_ptr { @@ -62,6 +69,9 @@ public: return _attachBotsUpdates.events(); } + void requestAddToMenu(not_null bot); + void removeFromMenu(not_null bot); + static void ClearAll(); private: @@ -72,13 +82,18 @@ private: const QString &username, Fn)> done); - void toggleInMenu(bool enabled, Fn callback); + void toggleInMenu( + not_null bot, + bool enabled, + Fn callback); void show( uint64 queryId, const QString &url, const QString &buttonText = QString()); - void requestAddToMenu(Fn callback); + void confirmAddToMenu( + AttachWebViewBot bot, + Fn callback = nullptr); void started(uint64 queryId); const not_null _session; @@ -95,6 +110,9 @@ private: uint64 _botsHash = 0; mtpRequestId _botsRequestId = 0; + UserData *_addToMenuBot = nullptr; + mtpRequestId _addToMenuId = 0; + std::vector _attachBots; rpl::event_stream<> _attachBotsUpdates; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index b5c72e76a..6415f1964 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -478,7 +478,17 @@ std::unique_ptr Show(Args &&args) { std::move(args.sendData), std::move(args.close), std::move(args.themeParams)); - result->showWebview(args.url, std::move(args.bottom)); + if (!result->showWebview(args.url, std::move(args.bottom))) { + const auto available = Webview::Availability(); + if (available.error != Webview::Available::Error::None) { + result->showWebviewError( + tr::lng_payments_webview_no_card(tr::now), + available); + } else { + result->showCriticalError({ + "Error: Could not initialize WebView." }); + } + } return result; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 1cd6c5377..b662a16ac 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -41,6 +41,9 @@ public: void showBox(object_ptr box); void showToast(const TextWithEntities &text); void showCriticalError(const TextWithEntities &text); + void showWebviewError( + const QString &text, + const Webview::Available &information); void updateThemeParams(const QByteArray &json); @@ -53,9 +56,6 @@ private: bool createWebview(); void showWebviewProgress(); void hideWebviewProgress(); - void showWebviewError( - const QString &text, - const Webview::Available &information); void setTitle(rpl::producer title); [[nodiscard]] bool progressWithBackground() const; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index dac8a48d3..411850cf4 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -383,16 +383,22 @@ void SessionNavigation::showPeerByLinkResolved( msgId = ShowAtUnreadMsgId; } const auto attachBotUsername = info.attachBotUsername; - if (user && user->isBot()) { + if (user + && user->isBot() + && user->botInfo->startToken != info.startToken) { user->botInfo->startToken = info.startToken; user->session().changes().peerUpdated( user, Data::PeerUpdate::Flag::BotStartToken); } - crl::on_main(this, [=] { - showPeerHistory(peer->id, params, msgId); - peer->session().attachWebView().request(peer, attachBotUsername); - }); + if (user && info.attachBotToggle) { + user->session().attachWebView().requestAddToMenu(user); + } else { + crl::on_main(this, [=] { + showPeerHistory(peer->id, params, msgId); + peer->session().attachWebView().request(peer, attachBotUsername); + }); + } } } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index b2ee2f6b3..e417f48ed 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -196,6 +196,7 @@ public: QString startToken; ChatAdminRights startAdminRights; QString attachBotUsername; + bool attachBotToggle = false; std::optional voicechatHash; FullMsgId clickFromMessageId; };