diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 470156400..0d0c1e25d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2268,6 +2268,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_reload_page" = "Reload Page"; "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_will_be_added" = "{bot} shortcuts will be added to the attachment options and the main menu."; +"lng_bot_side_menu_new" = "NEW"; "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"; diff --git a/Telegram/SourceFiles/data/data_bot_app.h b/Telegram/SourceFiles/data/data_bot_app.h index 7de4aefe8..715fb03c0 100644 --- a/Telegram/SourceFiles/data/data_bot_app.h +++ b/Telegram/SourceFiles/data/data_bot_app.h @@ -24,4 +24,5 @@ struct BotAppData { uint64 accessHash = 0; uint64 hash = 0; + bool hasSettings = false; }; diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index 3d1c241e4..876dadf87 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -124,6 +124,10 @@ constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); if (result && result->icon) { result->icon->forceToCache(true); } + if (const auto icon = result->icon) { + result->media = icon->createMediaView(); + icon->save(Data::FileOrigin(), {}); + } return result; } @@ -194,6 +198,78 @@ void ShowChooseBox( return result; } +void FillDisclaimerBox(not_null box, Fn done) { + const auto updateCheck = std::make_shared>(); + const auto validateCheck = std::make_shared>(); + + const auto callback = [=](Fn close) { + if (validateCheck && (*validateCheck)()) { + done(); + close(); + } + }; + + const auto padding = st::boxRowPadding; + Ui::ConfirmBox(box, { + .text = tr::lng_mini_apps_disclaimer_text( + tr::now, + Ui::Text::RichLangValue), + .confirmed = callback, + .confirmText = tr::lng_box_ok(), + .labelPadding = QMargins(padding.left(), 0, padding.right(), 0), + .title = tr::lng_mini_apps_disclaimer_title(), + }); + + auto checkView = std::make_unique( + st::defaultCheck, + false, + [=] { if (*updateCheck) { (*updateCheck)(); } }); + const auto check = checkView.get(); + const auto row = box->addRow( + object_ptr( + box.get(), + tr::lng_mini_apps_disclaimer_button( + lt_link, + rpl::single(Ui::Text::Link( + tr::lng_mini_apps_disclaimer_link(tr::now), + tr::lng_mini_apps_tos_url(tr::now))), + Ui::Text::WithEntities), + st::defaultBoxCheckbox, + std::move(checkView)), + { + st::boxRowPadding.left(), + st::boxRowPadding.left(), + st::boxRowPadding.right(), + 0, + }); + row->setAllowTextLines(5); + row->setClickHandlerFilter([=]( + const ClickHandlerPtr &link, + Qt::MouseButton button) { + ActivateClickHandler(row, link, ClickContext{ + .button = button, + .other = QVariant::fromValue(ClickHandlerContext{ + .show = box->uiShow(), + }) + }); + return false; + }); + + (*updateCheck) = [=] { row->update(); }; + + const auto showError = Ui::CheckView::PrepareNonToggledError( + check, + box->lifetime()); + + (*validateCheck) = [=] { + if (check->checked()) { + return true; + } + showError(); + return false; + }; +} + class BotAction final : public Ui::Menu::ItemBase { public: BotAction( @@ -818,10 +894,6 @@ void AttachWebView::requestBots() { _attachBots.reserve(data.vbots().v.size()); for (const auto &bot : data.vbots().v) { if (auto parsed = ParseAttachBot(_session, bot)) { - if (const auto icon = parsed->icon) { - parsed->media = icon->createMediaView(); - icon->save(Data::FileOrigin(), {}); - } _attachBots.push_back(std::move(*parsed)); } } @@ -832,6 +904,11 @@ void AttachWebView::requestBots() { }).send(); } +bool AttachWebView::showingDisclaimer(const AttachWebViewBot &bot) const { + return bot.disclaimerRequired + && !_disclaimerAccepted.contains(bot.user); +} + void AttachWebView::requestAddToMenu( not_null bot, std::optional startCommand) { @@ -885,9 +962,7 @@ void AttachWebView::requestAddToMenu( } } else if (!startCommand) { _bot = bot; - acceptDisclaimer(strong, [=] { - requestSimple(strong, bot, { .fromMainMenu = true }); - }); + requestSimple(strong, bot, { .fromMainMenu = true }); return true; } else if (const auto useTypes = chooseTypes & types) { const auto done = [=](not_null thread) { @@ -1128,6 +1203,7 @@ void AttachWebView::requestApp( _bot->id, data.vapp()); _app = received ? received : already; + _app->hasSettings = data.is_has_settings(); if (!_app) { cancel(); showToast(tr::lng_username_app_not_found(tr::now)); @@ -1140,8 +1216,8 @@ void AttachWebView::requestApp( requestAppView(false); } }).fail([=] { - cancel(); showToast(tr::lng_username_app_not_found(tr::now)); + cancel(); }).send(); } @@ -1190,13 +1266,14 @@ void AttachWebView::requestAppView(bool allowWrite) { return; } using Flag = MTPmessages_RequestAppWebView::Flag; + const auto app = _app; const auto flags = Flag::f_theme_params | (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param) | (allowWrite ? Flag::f_write_allowed : Flag(0)); _requestId = _session->api().request(MTPmessages_RequestAppWebView( MTP_flags(flags), _context->action.history->peer->input, - MTP_inputBotAppID(MTP_long(_app->id), MTP_long(_app->accessHash)), + MTP_inputBotAppID(MTP_long(app->id), MTP_long(app->accessHash)), MTP_string(_startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop") @@ -1204,7 +1281,7 @@ void AttachWebView::requestAppView(bool allowWrite) { _requestId = 0; const auto &data = result.data(); const auto queryId = uint64(); - show(queryId, qs(data.vurl())); + show(queryId, qs(data.vurl()), QString(), false, app); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { @@ -1256,93 +1333,17 @@ void AttachWebView::acceptDisclaimer( } else if (i->inactive) { requestAddToMenu(_bot, {}, controller, {}, {}); return; - } else if (!i->disclaimerRequired) { + } else if (!showingDisclaimer(*i)) { done(); return; } const auto weak = base::make_weak(this); - controller->show(Box([=](not_null box) { - const auto updateCheck = std::make_shared>(); - const auto validateCheck = std::make_shared>(); - - const auto callback = [=](Fn close) { - if (validateCheck && (*validateCheck)() && weak) { - const auto i = ranges::find( - _attachBots, - not_null(_bot), - &AttachWebViewBot::user); - if (i == end(_attachBots)) { - _attachBotsUpdates.fire({}); - } else if (i->inactive) { - requestAddToMenu(_bot, std::nullopt); - } else { - i->disclaimerRequired = false; - requestBots(); - done(); - } - close(); - } - }; - - Ui::ConfirmBox(box, { - .text = tr::lng_mini_apps_disclaimer_text( - tr::now, - Ui::Text::RichLangValue), - .confirmed = callback, - .confirmText = tr::lng_box_ok(), - .title = tr::lng_mini_apps_disclaimer_title(), - }); - - auto checkView = std::make_unique( - st::defaultCheck, - false, - [=] { if (*updateCheck) { (*updateCheck)(); } }); - const auto check = checkView.get(); - const auto row = box->addRow( - object_ptr( - box.get(), - tr::lng_mini_apps_disclaimer_button( - lt_link, - rpl::single(Ui::Text::Link( - tr::lng_mini_apps_disclaimer_link(tr::now), - tr::lng_mini_apps_tos_url(tr::now))), - Ui::Text::WithEntities), - st::defaultBoxCheckbox, - std::move(checkView)), - { - st::boxRowPadding.left(), - st::boxRowPadding.left(), - st::boxRowPadding.right(), - st::defaultBoxCheckbox.margin.bottom(), - }); - row->setAllowTextLines(5); - row->setClickHandlerFilter([=]( - const ClickHandlerPtr &link, - Qt::MouseButton button) { - ActivateClickHandler(row, link, ClickContext{ - .button = button, - .other = QVariant::fromValue(ClickHandlerContext{ - .show = box->uiShow(), - }) - }); - return false; - }); - - (*updateCheck) = [=] { row->update(); }; - - const auto showError = Ui::CheckView::PrepareNonToggledError( - check, - box->lifetime()); - - (*validateCheck) = [=] { - if (check->checked()) { - return true; - } - showError(); - return false; - }; - })); + controller->show(Box(FillDisclaimerBox, crl::guard(this, [=] { + _disclaimerAccepted.emplace(_bot); + _attachBotsUpdates.fire({}); + done(); + }))); } void AttachWebView::ClearAll() { @@ -1355,7 +1356,8 @@ void AttachWebView::show( uint64 queryId, const QString &url, const QString &buttonText, - bool allowClipboardRead) { + bool allowClipboardRead, + const BotAppData *app) { Expects(_bot != nullptr && _context != nullptr); auto title = Info::Profile::NameValue(_bot); @@ -1366,12 +1368,15 @@ void AttachWebView::show( _attachBots, not_null{ _bot }, &AttachWebViewBot::user); - const auto hasSettings = (attached != end(_attachBots)) - && !attached->inactive - && attached->hasSettings; + const auto hasSettings = app + ? app->hasSettings + : ((attached != end(_attachBots)) + && !attached->inactive + && attached->hasSettings); const auto hasOpenBot = !_context || (_bot != _context->action.history->peer); - const auto hasRemoveFromMenu = (attached != end(_attachBots)) + const auto hasRemoveFromMenu = !app + && (attached != end(_attachBots)) && (!attached->inactive || attached->inMainMenu); const auto buttons = (hasSettings ? Button::Settings : Button::None) | (hasOpenBot ? Button::OpenBot : Button::None) @@ -1473,16 +1478,25 @@ void AttachWebView::confirmAddToMenu( }); close(); }; - Ui::ConfirmBox(box, { - (bot.inMainMenu - ? tr::lng_bot_add_to_side_menu - : tr::lng_bot_add_to_menu)( - tr::now, - lt_bot, - Ui::Text::Bold(bot.name), - Ui::Text::WithEntities), - done, - }); + const auto disclaimer = showingDisclaimer(bot); + if (disclaimer) { + FillDisclaimerBox(box, [=] { + _disclaimerAccepted.emplace(bot.user); + _attachBotsUpdates.fire({}); + done([] {}); + }); + } else { + Ui::ConfirmBox(box, { + (bot.inMainMenu + ? tr::lng_bot_add_to_side_menu + : tr::lng_bot_add_to_menu)( + tr::now, + lt_bot, + Ui::Text::Bold(bot.name), + Ui::Text::WithEntities), + done, + }); + } if (bot.requestWriteAccess) { (*allowed) = box->addRow( object_ptr( @@ -1496,11 +1510,27 @@ void AttachWebView::confirmAddToMenu( st::urlAuthCheckbox), style::margins( st::boxRowPadding.left(), - st::boxPhotoCaptionSkip, + (disclaimer + ? st::boxPhotoCaptionSkip + : st::boxRowPadding.left()), st::boxRowPadding.right(), - st::boxPhotoCaptionSkip)); + st::boxRowPadding.left())); (*allowed)->setAllowTextLines(); } + if (disclaimer) { + if (!bot.requestWriteAccess) { + box->addRow(object_ptr( + box, + st::boxRowPadding.left())); + } + box->addRow(object_ptr( + box, + tr::lng_bot_will_be_added( + lt_bot, + rpl::single(Ui::Text::Bold(bot.name)), + Ui::Text::WithEntities), + st::boxLabel)); + } })); } diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 926312ea5..665000755 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -119,6 +119,7 @@ public: [[nodiscard]] rpl::producer<> attachBotsUpdates() const { return _attachBotsUpdates.events(); } + [[nodiscard]] bool showingDisclaimer(const AttachWebViewBot &bot) const; void requestAddToMenu( not_null bot, @@ -195,7 +196,8 @@ private: uint64 queryId, const QString &url, const QString &buttonText = QString(), - bool allowClipboardRead = false); + bool allowClipboardRead = false, + const BotAppData *app = nullptr); void confirmAddToMenu( AttachWebViewBot bot, Fn callback = nullptr); @@ -238,6 +240,7 @@ private: std::vector _attachBots; rpl::event_stream<> _attachBotsUpdates; + base::flat_set> _disclaimerAccepted; std::unique_ptr _panel; diff --git a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp index 070f84b29..22b2b3987 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp @@ -24,7 +24,9 @@ void ConfirmBox(not_null box, ConfirmBoxArgs &&args) { if (!v::is_null(args.text)) { const auto padding = st::boxPadding; - const auto use = withTitle + const auto use = args.labelPadding + ? *args.labelPadding + : withTitle ? QMargins(padding.left(), 0, padding.right(), padding.bottom()) : padding; const auto label = box->addRow( diff --git a/Telegram/SourceFiles/ui/boxes/confirm_box.h b/Telegram/SourceFiles/ui/boxes/confirm_box.h index b66be3d3a..b65a04361 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_box.h +++ b/Telegram/SourceFiles/ui/boxes/confirm_box.h @@ -30,6 +30,7 @@ struct ConfirmBoxArgs { const style::FlatLabel *labelStyle = nullptr; Fn labelFilter; + std::optional labelPadding; v::text::data title = v::null; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 45762cf00..97e9bb74b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -561,10 +561,6 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { _widget->showInner(std::move(outer)); _webviewParent = container; - container->paintRequest() | rpl::start_with_next([=] { - QPainter(container).fillRect(container->rect(), QColor(0, 128, 0, 255)); - }, container->lifetime()); - _webviewBottom = std::make_unique(_widget.get()); const auto bottom = _webviewBottom.get(); bottom->show(); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index e64a9b768..7974b64a4 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -211,15 +211,19 @@ void SetupMenuBots( ) | rpl::then( bots->attachBotsUpdates() ) | rpl::start_with_next([=] { + const auto width = wrap->widthNoMargins(); wrap->clear(); for (const auto &bot : bots->attachBots()) { if (!bot.inMainMenu) { continue; } const auto button = Settings::AddButton( - container, + wrap, rpl::single(bot.name), st::mainMenuButton); + const auto menu = button->lifetime().make_state< + base::unique_qptr + >(); const auto icon = Ui::CreateChild( button.get(), bot.media); @@ -229,12 +233,57 @@ void SetupMenuBots( st::mainMenuButton.iconLeft, (height - icon->height()) / 2); }, button->lifetime()); - button->setClickedCallback([=] { - bots->requestSimple(controller, bot.user, { - .fromMainMenu = true, - }); - }); + const auto user = bot.user; + button->setAcceptBoth(true); + button->clicks( + ) | rpl::start_with_next([=](Qt::MouseButton which) { + if (which == Qt::LeftButton) { + bots->requestSimple(controller, user, { + .fromMainMenu = true, + }); + } else { + (*menu) = nullptr; + (*menu) = base::make_unique_q( + button, + st::popupMenuWithIcons); + (*menu)->addAction( + tr::lng_bot_remove_from_menu(tr::now), + [=] { bots->removeFromMenu(user); }, + &st::menuIconDelete); + (*menu)->popup(QCursor::pos()); + } + }, button->lifetime()); + + const auto badge = bots->showingDisclaimer(bot) + ? Ui::CreateChild>( + button.get(), + object_ptr( + button, + tr::lng_bot_side_menu_new(), + st::settingsPremiumNewBadge), + st::settingsPremiumNewBadgePadding) + : nullptr; + if (badge) { + badge->setAttribute(Qt::WA_TransparentForMouseEvents); + badge->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(badge); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgActive); + const auto r = st::settingsPremiumNewBadgePadding.left(); + p.drawRoundedRect(badge->rect(), r, r); + }, badge->lifetime()); + + button->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + badge->moveToRight( + st::mainMenuButton.padding.right(), + (size.height() - badge->height()) / 2, + size.width()); + }, badge->lifetime()); + } } + wrap->resizeToWidth(width); }, wrap->lifetime()); }