From 21487641c1b5eeb4d5abb5f03a1b8d4d767ef567 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 8 Nov 2024 12:02:59 +0400 Subject: [PATCH] Support miniapp fullscreen API. --- .../SourceFiles/core/local_url_handlers.cpp | 1 + .../inline_bots/bot_attach_web_view.cpp | 47 +++++++++++--- .../inline_bots/bot_attach_web_view.h | 12 +++- .../ui/chat/attach/attach_bot_webview.cpp | 62 +++++++++++++++++++ .../ui/chat/attach/attach_bot_webview.h | 4 ++ .../window/window_session_controller.cpp | 10 ++- .../window_session_controller_link_info.h | 1 + 7 files changed, 123 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index a8815478f..adcc40385 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -618,6 +618,7 @@ bool ResolveUsernameOrPhone( .startAutoSubmit = myContext.botStartAutoSubmit, .botAppName = (appname.isEmpty() ? postParam : appname), .botAppForceConfirmation = myContext.mayShowConfirmation, + .botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q), .attachBotUsername = params.value(u"attach"_q), .attachBotToggleCommand = (params.contains(u"startattach"_q) ? params.value(u"startattach"_q) diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index c3290ea24..674170696 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -914,6 +914,7 @@ void WebViewInstance::requestButton() { using Flag = MTPmessages_RequestWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestWebView( MTP_flags(Flag::f_theme_params + | (_context.fullscreen ? Flag::f_fullscreen : Flag(0)) | (_button.url.isEmpty() ? Flag(0) : Flag::f_url) | (_button.startCommand.isEmpty() ? Flag(0) @@ -936,7 +937,11 @@ void WebViewInstance::requestButton() { : MTP_inputPeerEmpty()) )).done([=](const MTPWebViewResult &result) { const auto &data = result.data(); - show(qs(data.vurl()), data.vquery_id().value_or_empty()); + show({ + .url = qs(data.vurl()), + .queryId = data.vquery_id().value_or_empty(), + .fullscreen = data.is_fullscreen(), + }); }).fail([=](const MTP::Error &error) { _parentShow->showToast(error.type()); if (error.type() == u"BOT_INVALID"_q) { @@ -950,6 +955,7 @@ void WebViewInstance::requestSimple() { using Flag = MTPmessages_RequestSimpleWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestSimpleWebView( MTP_flags(Flag::f_theme_params + | (_context.fullscreen ? Flag::f_fullscreen : Flag(0)) | (v::is(_source) ? (Flag::f_url | Flag::f_from_switch_webview) : v::is(_source) @@ -964,7 +970,11 @@ void WebViewInstance::requestSimple() { MTP_dataJSON(MTP_bytes(botThemeParams().json)), MTP_string("tdesktop") )).done([=](const MTPWebViewResult &result) { - show(qs(result.data().vurl())); + const auto &data = result.data(); + show({ + .url = qs(data.vurl()), + .fullscreen = data.is_fullscreen(), + }); }).fail([=](const MTP::Error &error) { _parentShow->showToast(error.type()); close(); @@ -975,6 +985,7 @@ void WebViewInstance::requestMain() { using Flag = MTPmessages_RequestMainWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestMainWebView( MTP_flags(Flag::f_theme_params + | (_context.fullscreen ? Flag::f_fullscreen : Flag(0)) | (_button.startCommand.isEmpty() ? Flag() : Flag::f_start_param) @@ -989,7 +1000,11 @@ void WebViewInstance::requestMain() { MTP_dataJSON(MTP_bytes(botThemeParams().json)), MTP_string("tdesktop") )).done([=](const MTPWebViewResult &result) { - show(qs(result.data().vurl())); + const auto &data = result.data(); + show({ + .url = qs(data.vurl()), + .fullscreen = data.is_fullscreen(), + }); }).fail([=](const MTP::Error &error) { _parentShow->showToast(error.type()); close(); @@ -1003,6 +1018,7 @@ void WebViewInstance::requestApp(bool allowWrite) { using Flag = MTPmessages_RequestAppWebView::Flag; const auto app = _app; const auto flags = Flag::f_theme_params + | (_context.fullscreen ? Flag::f_fullscreen : Flag(0)) | (_appStartParam.isEmpty() ? Flag(0) : Flag::f_start_param) | (allowWrite ? Flag::f_write_allowed : Flag(0)); _requestId = _session->api().request(MTPmessages_RequestAppWebView( @@ -1014,7 +1030,11 @@ void WebViewInstance::requestApp(bool allowWrite) { MTP_string("tdesktop") )).done([=](const MTPWebViewResult &result) { _requestId = 0; - show(qs(result.data().vurl())); + const auto &data = result.data(); + show({ + .url = qs(data.vurl()), + .fullscreen = data.is_fullscreen(), + }); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { @@ -1093,7 +1113,7 @@ void WebViewInstance::maybeChooseAndRequestButton(PeerTypes supported) { close(); } -void WebViewInstance::show(const QString &url, uint64 queryId) { +void WebViewInstance::show(ShowArgs &&args) { auto title = Info::Profile::NameValue(_bot); auto titleBadge = _bot->isVerified() ? object_ptr(_parentShow->toastParent()) @@ -1131,18 +1151,19 @@ void WebViewInstance::show(const QString &url, uint64 queryId) { || v::is(_source) || (attached != end(bots) && (attached->inAttachMenu || attached->inMainMenu)); - _panelUrl = url; + _panelUrl = args.url; _panel = Ui::BotWebView::Show({ - .url = url, + .url = args.url, .storageId = _session->local().resolveStorageIdBots(), .title = std::move(title), .titleBadge = std::move(titleBadge), .bottom = rpl::single('@' + _bot->username()), .delegate = static_cast(this), .menuButtons = buttons, + .fullscreen = args.fullscreen, .allowClipboardRead = allowClipboardRead, }); - started(queryId); + started(args.queryId); if (const auto strong = PendingActivation.get()) { if (strong == this) { @@ -1618,20 +1639,25 @@ void AttachWebView::openByUsername( not_null controller, const Api::SendAction &action, const QString &botUsername, - const QString &startCommand) { + const QString &startCommand, + bool fullscreen) { if (botUsername.isEmpty() - || (_botUsername == botUsername && _startCommand == startCommand)) { + || (_botUsername == botUsername + && _startCommand == startCommand + && _fullScreenRequested == fullscreen)) { return; } cancel(); _botUsername = botUsername; _startCommand = startCommand; + _fullScreenRequested = fullscreen; const auto weak = base::make_weak(controller); const auto show = controller->uiShow(); resolveUsername(show, crl::guard(weak, [=](not_null peer) { _botUsername = QString(); const auto token = base::take(_startCommand); + const auto fullscreen = base::take(_fullScreenRequested); const auto bot = peer->asUser(); if (!bot || !bot->isBot()) { @@ -1646,6 +1672,7 @@ void AttachWebView::openByUsername( .context = { .controller = controller, .action = action, + .fullscreen = fullscreen, }, .button = { .startCommand = token }, .source = InlineBots::WebViewSourceLinkAttachMenu{}, diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 4958ba19d..a879cdd2c 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -187,6 +187,7 @@ struct WebViewContext { base::weak_ptr controller; Dialogs::EntryState dialogsEntryState; std::optional action; + bool fullscreen = false; bool maySkipConfirmation = false; }; @@ -234,7 +235,12 @@ private: void confirmOpen(Fn done); void confirmAppOpen(bool writeAccess, Fn done); - void show(const QString &url, uint64 queryId = 0); + struct ShowArgs { + QString url; + uint64 queryId = 0; + bool fullscreen = false; + }; + void show(ShowArgs &&args); void showGame(); void started(uint64 queryId); @@ -291,7 +297,8 @@ public: not_null controller, const Api::SendAction &action, const QString &botUsername, - const QString &startCommand); + const QString &startCommand, + bool fullscreen); void cancel(); @@ -360,6 +367,7 @@ private: QString _botUsername; QString _startCommand; + bool _fullScreenRequested = false; mtpRequestId _requestId = 0; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 41fd093d0..36de9475d 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -38,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include +#include namespace Ui::BotWebView { namespace { @@ -369,11 +371,13 @@ Panel::Panel( object_ptr titleBadge, not_null delegate, MenuButtons menuButtons, + bool fullscreen, bool allowClipboardRead) : _storageId(storageId) , _delegate(delegate) , _menuButtons(menuButtons) , _widget(std::make_unique()) +, _fullscreen(fullscreen) , _allowClipboardRead(allowClipboardRead) { _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->setInnerSize(st::botWebViewPanelSize, true); @@ -382,7 +386,9 @@ Panel::Panel( ) | rpl::start_with_next([=](bool fullscreen) { _widget->toggleFullScreen(fullscreen); layoutButtons(); + sendFullScreen(); sendSafeArea(); + sendContentSafeArea(); }, _widget->lifetime()); _widget->closeRequests( @@ -762,6 +768,36 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { sendViewport(); } else if (command == "web_app_request_safe_area") { sendSafeArea(); + } else if (command == "web_app_request_content_safe_area") { + sendContentSafeArea(); + } else if (command == "web_app_request_fullscreen") { + if (!_fullscreen.current()) { + _fullscreen = true; + } else { + sendFullScreen(); + } + } else if (command == "web_app_exit_fullscreen") { + if (_fullscreen.current()) { + _fullscreen = false; + } else { + sendFullScreen(); + } + } else if (command == "web_app_check_home_screen") { + postEvent("home_screen_checked", "{ status: \"unsupported\" }"); + } else if (command == "web_app_start_accelerometer") { + postEvent("accelerometer_failed", "{ error: \"UNSUPPORTED\" }"); + } else if (command == "web_app_start_device_orientation") { + postEvent( + "device_orientation_failed", + "{ error: \"UNSUPPORTED\" }"); + } else if (command == "web_app_start_gyroscope") { + postEvent("gyroscope_failed", "{ error: \"UNSUPPORTED\" }"); + } else if (command == "web_app_check_location") { + postEvent("location_checked", "{ available: false }"); + } else if (command == "web_app_request_location") { + postEvent("location_requested", "{ available: false }"); + } else if (command == "web_app_biometry_get_info") { + postEvent("biometry_info_received", "{ available: false }"); } else if (command == "web_app_open_tg_link") { openTgLink(arguments); } else if (command == "web_app_open_link") { @@ -846,11 +882,36 @@ void Panel::sendViewport() { "is_expanded: true }"); } +void Panel::sendFullScreen() { + postEvent("fullscreen_changed", _fullscreen.current() + ? "{ is_fullscreen: true }" + : "{ is_fullscreen: false }"); +} + void Panel::sendSafeArea() { postEvent("safe_area_changed", "{ top: 0, right: 0, bottom: 0, left: 0 }"); } +void Panel::sendContentSafeArea() { + const auto shift = st::separatePanelClose.rippleAreaPosition.y(); + const auto top = _fullscreen.current() + ? (shift + st::fullScreenPanelClose.height + (shift / 2)) + : 0; + const auto scaled = top * style::DevicePixelRatio(); + auto report = 0; + if (const auto screen = QGuiApplication::primaryScreen()) { + const auto dpi = screen->logicalDotsPerInch(); + const auto ratio = screen->devicePixelRatio(); + const auto basePair = screen->handle()->logicalBaseDpi(); + const auto base = (basePair.first + basePair.second) * 0.5; + const auto systemScreenScale = dpi * ratio / base; + report = int(base::SafeRound(scaled / systemScreenScale)); + } + postEvent("content_safe_area_changed", + u"{ top: %1, right: 0, bottom: 0, left: 0 }"_q.arg(report)); +} + void Panel::setTitle(rpl::producer title) { _widget->setTitle(std::move(title)); } @@ -1662,6 +1723,7 @@ std::unique_ptr Show(Args &&args) { std::move(args.titleBadge), args.delegate, args.menuButtons, + args.fullscreen, args.allowClipboardRead); const auto params = args.delegate->botThemeParams(); if (!result->showWebview(args.url, params, std::move(args.bottom))) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 05a64c3c6..a42bf6634 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -79,6 +79,7 @@ public: object_ptr titleBadge, not_null delegate, MenuButtons menuButtons, + bool fullscreen, bool allowClipboardRead); ~Panel(); @@ -148,6 +149,8 @@ private: void closeWithConfirmation(); void sendViewport(); void sendSafeArea(); + void sendContentSafeArea(); + void sendFullScreen(); using EventData = std::variant; void postEvent(const QString &event); @@ -198,6 +201,7 @@ struct Args { rpl::producer bottom; not_null delegate; MenuButtons menuButtons; + bool fullscreen = false; bool allowClipboardRead = false; }; [[nodiscard]] std::unique_ptr Show(Args &&args); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 0ec6d195b..bb9bddf9e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -637,6 +637,7 @@ void SessionNavigation::showPeerByLinkResolved( .context = { .controller = parentController(), .action = action, + .fullscreen = info.botAppFullScreen, .maySkipConfirmation = !info.botAppForceConfirmation, }, .button = { .startCommand = info.startToken }, @@ -697,14 +698,18 @@ void SessionNavigation::showPeerByLinkResolved( parentController(), Api::SendAction(history), attachBotUsername, - info.attachBotToggleCommand.value_or(QString())); + info.attachBotToggleCommand.value_or(QString()), + info.botAppFullScreen); }); } else if (bot && info.attachBotMainOpen) { const auto startCommand = info.attachBotToggleCommand.value_or( QString()); bot->session().attachWebView().open({ .bot = bot, - .context = { .controller = parentController() }, + .context = { + .controller = parentController(), + .fullscreen = info.botAppFullScreen, + }, .button = { .startCommand = startCommand }, .source = InlineBots::WebViewSourceLinkBotProfile{ .token = startCommand, @@ -728,6 +733,7 @@ void SessionNavigation::showPeerByLinkResolved( ? Api::SendAction( contextUser->owner().history(contextUser)) : std::optional()), + .fullscreen = info.botAppFullScreen, }, .button = { .startCommand = *info.attachBotToggleCommand }, .source = InlineBots::WebViewSourceLinkAttachMenu{ diff --git a/Telegram/SourceFiles/window/window_session_controller_link_info.h b/Telegram/SourceFiles/window/window_session_controller_link_info.h index 329dc4d65..145da2d41 100644 --- a/Telegram/SourceFiles/window/window_session_controller_link_info.h +++ b/Telegram/SourceFiles/window/window_session_controller_link_info.h @@ -49,6 +49,7 @@ struct PeerByLinkInfo { bool joinChannel = false; QString botAppName; bool botAppForceConfirmation = false; + bool botAppFullScreen = false; QString attachBotUsername; std::optional attachBotToggleCommand; bool attachBotMainOpen = false;