Improve main menu bots disclaimer acceptance.

This commit is contained in:
John Preston 2023-09-08 21:55:28 +04:00
parent 229f7a2c15
commit ef969df86e
8 changed files with 205 additions and 121 deletions

View File

@ -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";

View File

@ -24,4 +24,5 @@ struct BotAppData {
uint64 accessHash = 0;
uint64 hash = 0;
bool hasSettings = false;
};

View File

@ -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<Ui::GenericBox*> box, Fn<void()> done) {
const auto updateCheck = std::make_shared<Fn<void()>>();
const auto validateCheck = std::make_shared<Fn<bool()>>();
const auto callback = [=](Fn<void()> 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<Ui::CheckView>(
st::defaultCheck,
false,
[=] { if (*updateCheck) { (*updateCheck)(); } });
const auto check = checkView.get();
const auto row = box->addRow(
object_ptr<Ui::Checkbox>(
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<UserData*> bot,
std::optional<QString> 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<Data::Thread*> 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<Ui::GenericBox*> box) {
const auto updateCheck = std::make_shared<Fn<void()>>();
const auto validateCheck = std::make_shared<Fn<bool()>>();
const auto callback = [=](Fn<void()> 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<Ui::CheckView>(
st::defaultCheck,
false,
[=] { if (*updateCheck) { (*updateCheck)(); } });
const auto check = checkView.get();
const auto row = box->addRow(
object_ptr<Ui::Checkbox>(
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<Ui::Checkbox>(
@ -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<Ui::FixedHeightWidget>(
box,
st::boxRowPadding.left()));
}
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_bot_will_be_added(
lt_bot,
rpl::single(Ui::Text::Bold(bot.name)),
Ui::Text::WithEntities),
st::boxLabel));
}
}));
}

View File

@ -119,6 +119,7 @@ public:
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
return _attachBotsUpdates.events();
}
[[nodiscard]] bool showingDisclaimer(const AttachWebViewBot &bot) const;
void requestAddToMenu(
not_null<UserData*> 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<void()> callback = nullptr);
@ -238,6 +240,7 @@ private:
std::vector<AttachWebViewBot> _attachBots;
rpl::event_stream<> _attachBotsUpdates;
base::flat_set<not_null<UserData*>> _disclaimerAccepted;
std::unique_ptr<Ui::BotWebView::Panel> _panel;

View File

@ -24,7 +24,9 @@ void ConfirmBox(not_null<Ui::GenericBox*> 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(

View File

@ -30,6 +30,7 @@ struct ConfirmBoxArgs {
const style::FlatLabel *labelStyle = nullptr;
Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> labelFilter;
std::optional<QMargins> labelPadding;
v::text::data title = v::null;

View File

@ -561,10 +561,6 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
_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<RpWidget>(_widget.get());
const auto bottom = _webviewBottom.get();
bottom->show();

View File

@ -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<Ui::PopupMenu>
>();
const auto icon = Ui::CreateChild<InlineBots::MenuBotIcon>(
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<Ui::PopupMenu>(
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<Ui::PaddingWrap<Ui::FlatLabel>>(
button.get(),
object_ptr<Ui::FlatLabel>(
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());
}