Added api support of match code to url auth box.

This commit is contained in:
23rd
2026-02-13 12:10:06 +03:00
committed by John Preston
parent a83890b03e
commit 2418fb311e
5 changed files with 318 additions and 29 deletions
+2
View File
@@ -4733,6 +4733,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_url_auth_phone_toast_bad_title" = "Login failed";
"lng_url_auth_phone_toast_bad" = "Please try logging in to {domain} again.";
"lng_url_auth_phone_toast_bad_expired" = "This link is expired.";
"lng_url_auth_match_code_title" = "Tap the emoji shown on your other device";
"lng_url_auth_match_code_info" = "Login request from {domain}";
"lng_bot_start" = "Start";
"lng_bot_choose_group" = "Select a Group";
+13
View File
@@ -809,11 +809,24 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) {
urlAuthCheckbox: Checkbox(defaultBoxCheckbox) {
width: 240px;
}
urlAuthCodesTitle: FlatLabel(defaultPeerListAbout) {
textFg: boxTitleFg;
style: TextStyle(defaultTextStyle) {
font: boxTitleFont;
}
}
urlAuthCheckboxAbout: FlatLabel(defaultPeerListAbout) {
style: TextStyle(boxTextStyle) {
font: font(12px);
}
}
urlAuthCodesButton: RoundButton(defaultLightButton) {
height: 58px;
textTop: 19px;
style: TextStyle(semiboldTextStyle) {
font: font(20px semibold);
}
}
urlAuthBoxRowTopLabel: FlatLabel(boxLabel) {
maxHeight: 30px;
}
+129 -21
View File
@@ -38,14 +38,18 @@ namespace UrlAuthBox {
namespace {
using AnotherSessionFactory = Fn<not_null<Main::Session*>()>;
using OnUserChangedCallback = Fn<void(Fn<void()>)>;
struct SwitchAccountResult {
not_null<Ui::RpWidget*> widget;
Ui::RpWidget *widget = nullptr;
AnotherSessionFactory anotherSession;
OnUserChangedCallback setOnUserChanged;
Fn<void(UserId)> updateUserIdHint;
};
[[nodiscard]] SwitchAccountResult AddAccountsMenu(
not_null<Ui::RpWidget*> parent) {
not_null<Ui::RpWidget*> parent,
UserId userIdHint = UserId()) {
const auto session = &Core::App().domain().active().session();
const auto widget = Ui::CreateChild<SwitchableUserpicButton>(
parent,
@@ -53,15 +57,33 @@ struct SwitchAccountResult {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
UserData *currentUser = nullptr;
Fn<void()> onUserChanged;
};
const auto state = widget->lifetime().make_state<State>();
state->currentUser = session->user();
const auto isCurrentTest = session->isTestMode();
const auto findHintedUser = [&]() -> UserData* {
if (!userIdHint) {
return session->user().get();
}
for (const auto &account : Core::App().domain().orderedAccounts()) {
if (!account->sessionExists()
|| (account->session().isTestMode() != isCurrentTest)) {
continue;
}
if (account->session().userId() == userIdHint) {
return account->session().user();
}
}
return session->user().get();
};
state->currentUser = findHintedUser();
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
parent,
state->currentUser,
st::restoreUserpicIcon);
widget->setUserpic(userpic);
const auto isCurrentTest = session->isTestMode();
const auto filtered = [=] {
auto result = std::vector<not_null<Main::Session*>>();
for (const auto &account : Core::App().domain().orderedAccounts()) {
@@ -90,6 +112,9 @@ struct SwitchAccountResult {
user,
st::restoreUserpicIcon);
widget->setUserpic(newUserpic);
if (state->onUserChanged) {
state->onUserChanged();
}
});
auto owned = base::make_unique_q<Ui::Menu::Action>(
state->menu->menu(),
@@ -116,6 +141,25 @@ struct SwitchAccountResult {
return {
widget,
[=] { return &state->currentUser->session(); },
[=](Fn<void()> callback) { state->onUserChanged = callback; },
[=](UserId newUserIdHint) {
const auto isCurrentTest = session->isTestMode();
for (const auto &acc : Core::App().domain().orderedAccounts()) {
if (!acc->sessionExists()
|| (acc->session().isTestMode() != isCurrentTest)) {
continue;
}
if (acc->session().userId() == newUserIdHint) {
state->currentUser = acc->session().user();
const auto next = Ui::CreateChild<Ui::UserpicButton>(
parent,
state->currentUser,
st::restoreUserpicIcon);
widget->setUserpic(next);
break;
}
}
},
};
}
@@ -269,20 +313,26 @@ void RequestButton(
};
const auto callback = [=](Result result) {
if (!result.auth) {
session->api().request(MTPmessages_DeclineUrlAuth(
MTP_string(url)
)).send();
finishWithUrl(url, false);
} else if (session->data().message(itemId)) {
using Flag = MTPmessages_AcceptUrlAuth::Flag;
const auto flags = Flag(0)
| (result.allowWrite ? Flag::f_write_allowed : Flag(0))
| (result.sharePhone ? Flag::f_share_phone_number : Flag(0))
| (result.matchCode.isEmpty() ? Flag(0) : Flag::f_match_code)
| (Flag::f_peer | Flag::f_msg_id | Flag::f_button_id);
session->api().request(MTPmessages_AcceptUrlAuth(
MTP_flags(flags),
inputPeer,
MTP_int(itemId.msg),
MTP_int(buttonId),
MTPstring(), // #TODO auth url
MTPstring()
MTPstring(),
result.matchCode.isEmpty()
? MTPstring()
: MTP_string(result.matchCode)
)).done([=](const MTPUrlAuthResult &result) {
const auto accepted = result.match(
[](const MTPDurlAuthResultAccepted &data) {
@@ -328,8 +378,22 @@ void RequestUrl(
: nullptr;
const auto requestPhone = request.is_request_phone_number();
const auto domain = qs(request.vdomain());
const auto userIdHint = request.vuser_id_hint()
? peerToUser(peerFromUser(*request.vuser_id_hint()))
: UserId();
const auto matchCodes = [&] {
auto result = QStringList();
if (const auto codes = request.vmatch_codes()) {
for (const auto &code : codes->v) {
result.push_back(qs(code));
}
}
return result;
}();
const auto box = std::make_shared<base::weak_qptr<Ui::BoxContent>>();
const auto authAccepted = std::make_shared<bool>(false);
const auto finishWithUrl = [=](const QString &url, bool accepted) {
*authAccepted = accepted;
if (*box) {
(*box)->closeBox();
}
@@ -341,25 +405,31 @@ void RequestUrl(
};
const auto anotherSessionFactory
= std::make_shared<AnotherSessionFactory>(nullptr);
const auto resolveSession = [=] {
return (*anotherSessionFactory) ? (*anotherSessionFactory)() : session;
};
const auto sendRequest = [=](Result result) {
if (!result.auth) {
resolveSession()->api().request(MTPmessages_DeclineUrlAuth(
MTP_string(url)
)).send();
finishWithUrl(url, false);
} else {
const auto sharePhone = result.sharePhone;
using Flag = MTPmessages_AcceptUrlAuth::Flag;
const auto flags = Flag::f_url
| (result.allowWrite ? Flag::f_write_allowed : Flag(0))
| (sharePhone ? Flag::f_share_phone_number : Flag(0));
const auto currentSession = anotherSessionFactory
? (*anotherSessionFactory)()
: session;
currentSession->api().request(MTPmessages_AcceptUrlAuth(
| (sharePhone ? Flag::f_share_phone_number : Flag(0))
| (result.matchCode.isEmpty() ? Flag(0) : Flag::f_match_code);
resolveSession()->api().request(MTPmessages_AcceptUrlAuth(
MTP_flags(flags),
MTPInputPeer(),
MTPint(), // msg_id
MTPint(), // button_id
MTP_string(url),
MTPstring()
result.matchCode.isEmpty()
? MTPstring()
: MTP_string(result.matchCode)
)).done([=](const MTPUrlAuthResult &result) {
const auto accepted = result.match(
[](const MTPDurlAuthResultAccepted &data) {
@@ -408,6 +478,35 @@ void RequestUrl(
const auto ip = qs(request.vip().value_or("Unknown IP"));
const auto region = qs(request.vregion().value_or("Unknown region"));
*box = show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto accountResult = box->lifetime().make_state<
SwitchAccountResult>(nullptr);
const auto matchCodesShared = box->lifetime().make_state<
rpl::variable<QStringList>>(matchCodes);
const auto reloadRequest = [=] {
using Flag = MTPmessages_RequestUrlAuth::Flag;
const auto currentSession = resolveSession();
currentSession->api().request(MTPmessages_RequestUrlAuth(
MTP_flags(Flag::f_url),
MTPInputPeer(),
MTPint(), // msg_id
MTPint(), // button_id
MTP_string(url)
)).done([=](const MTPUrlAuthResult &result) {
result.match([&](const MTPDurlAuthResultRequest &data) {
const auto newUserId = data.vuser_id_hint()
? peerToUser(peerFromUser(*data.vuser_id_hint()))
: UserId();
accountResult->updateUserIdHint(newUserId);
auto newCodes = QStringList();
if (const auto codes = data.vmatch_codes()) {
for (const auto &code : codes->v) {
newCodes.push_back(qs(code));
}
}
*matchCodesShared = newCodes;
}, [](const auto &) {});
}).send();
};
const auto callback = [=](Result result) {
if (!requestPhone) {
return sendRequest(result);
@@ -422,9 +521,7 @@ void RequestUrl(
close();
};
};
const auto currentSession = anotherSessionFactory
? (*anotherSessionFactory)()
: session;
const auto currentSession = resolveSession();
const auto capitalized = [=](const QString &value) {
return value.left(1).toUpper() + value.mid(1).toLower();
};
@@ -461,15 +558,26 @@ void RequestUrl(
browser,
device,
ip,
region);
region,
matchCodesShared->value());
const auto content = box->verticalLayout();
const auto accountResult = AddAccountsMenu(content);
content->widthValue() | rpl::on_next([=, w = accountResult.widget] {
*accountResult = AddAccountsMenu(box->verticalLayout(), userIdHint);
box->verticalLayout()->widthValue(
) | rpl::on_next([=, w = (*accountResult).widget] {
w->moveToRight(st::lineWidth * 4, 0);
}, accountResult.widget->lifetime());
*anotherSessionFactory = accountResult.anotherSession;
}, (*accountResult).widget->lifetime());
*anotherSessionFactory = (*accountResult).anotherSession;
(*accountResult).setOnUserChanged(reloadRequest);
}));
if (*box) {
(*box)->boxClosing() | rpl::on_next([=] {
if (!(*authAccepted)) {
resolveSession()->api().request(MTPmessages_DeclineUrlAuth(
MTP_string(url)
)).send();
}
}, (*box)->lifetime());
}
}
} // namespace UrlAuthBox
@@ -14,8 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/horizontal_fit_container.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/tooltip.h"
#include "ui/emoji_config.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "styles/style_boxes.h"
@@ -26,6 +28,118 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace UrlAuthBox {
namespace {
void PrepareFullWidthRoundButton(
not_null<Ui::RoundButton*> button,
not_null<Ui::VerticalLayout*> content,
const style::margins &padding) {
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
button->setFullRadius(true);
const auto paddingHorizontal = padding.left() + padding.right();
content->widthValue() | rpl::on_next([=](int w) {
button->resize(w - paddingHorizontal, button->height());
}, button->lifetime());
}
void ShowMatchCodesBox(
not_null<Ui::GenericBox*> box,
const QString &domain,
const QStringList &codes,
Fn<void(QString)> callback) {
box->setWidth(st::boxWidth);
box->setStyle(st::futureOwnerBox);
const auto content = box->verticalLayout();
const auto &buttonStyle = st::defaultLightButton;
Ui::AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_url_auth_match_code_title(),
st::urlAuthCodesTitle),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto buttons = content->add(
object_ptr<Ui::HorizontalFitContainer>(
content,
st::boxRowPadding.left() * 2),
st::boxRowPadding);
buttons->resize(0, st::urlAuthCodesButton.height);
for (const auto &code : codes) {
auto emojiLength = 0;
const auto emoji = Ui::Emoji::Find(code, &emojiLength);
const auto emojiCode = (emoji && (emojiLength == code.size()));
const auto button = Ui::CreateChild<Ui::RoundButton>(
buttons,
rpl::single(emojiCode ? QString() : code),
st::urlAuthCodesButton);
if (emojiCode) {
button->setTextFgOverride(QColor(Qt::transparent));
const auto overlay = Ui::CreateChild<Ui::RpWidget>(button);
overlay->setAttribute(Qt::WA_TransparentForMouseEvents);
overlay->show();
button->sizeValue() | rpl::on_next([=](QSize size) {
overlay->resize(size);
}, overlay->lifetime());
overlay->paintOn([=](QPainter &p) {
const auto size = Ui::Emoji::GetSizeLarge();
const auto visible = size / style::DevicePixelRatio();
Ui::Emoji::Draw(
p,
emoji,
size,
(overlay->width() - visible) / 2,
(overlay->height() - visible) / 2);
});
}
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
button->setFullRadius(true);
button->setClickedCallback([=] {
box->closeBox();
callback(code);
});
buttons->add(button);
}
Ui::AddSkip(content);
const auto domainUrl = qthelp::validate_url(domain);
if (!domainUrl.isEmpty()) {
Ui::AddSkip(content);
Ui::AddSkip(content);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_url_auth_login_title(
lt_domain,
rpl::single(Ui::Text::Link(domain, domainUrl)),
tr::marked),
st::urlAuthCheckboxAbout),
st::boxRowPadding,
style::al_top);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto &padding = st::boxRowPadding;
const auto button = content->add(
object_ptr<Ui::RoundButton>(
content,
tr::lng_cancel(),
st::attentionBoxButton),
padding);
PrepareFullWidthRoundButton(button, content, padding);
button->setClickedCallback([=] {
box->closeBox();
});
}
}
} // namespace
SwitchableUserpicButton::SwitchableUserpicButton(
@@ -252,8 +366,10 @@ void ShowDetails(
const QString &browser,
const QString &platform,
const QString &ip,
const QString &region) {
const QString &region,
rpl::producer<QStringList> matchCodes) {
box->setWidth(st::boxWidth);
box->setStyle(st::futureOwnerBox);
const auto content = box->verticalLayout();
@@ -342,13 +458,61 @@ void ShowDetails(
Ui::AddSkip(content);
}
box->addButton(tr::lng_url_auth_login_button(), [=] {
callback({
.auth = true,
.allowWrite = (allowMessages && allowMessages->toggled()),
struct State {
QStringList matchCodes;
};
const auto state = box->lifetime().make_state<State>();
std::move(matchCodes) | rpl::on_next([=](const QStringList &codes) {
state->matchCodes = codes;
}, box->lifetime());
{
const auto &padding = st::boxRowPadding;
const auto button = content->add(
object_ptr<Ui::RoundButton>(
content,
tr::lng_url_auth_login_button(),
st::defaultLightButton),
padding);
PrepareFullWidthRoundButton(button, content, padding);
button->setClickedCallback([=] {
if (state->matchCodes.isEmpty()) {
callback({
.auth = true,
.allowWrite = (allowMessages && allowMessages->toggled()),
});
return;
}
box->uiShow()->show(Box([=](
not_null<Ui::GenericBox*> matchCodesBox) {
ShowMatchCodesBox(
matchCodesBox,
domain,
state->matchCodes,
[=](QString matchCode) {
callback({
.auth = true,
.allowWrite = (allowMessages
&& allowMessages->toggled()),
.matchCode = std::move(matchCode),
});
});
}));
});
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
{
const auto &padding = st::boxRowPadding;
const auto button = content->add(
object_ptr<Ui::RoundButton>(
content,
tr::lng_suggest_action_decline(),
st::attentionBoxButton),
padding);
PrepareFullWidthRoundButton(button, content, padding);
button->setClickedCallback([=] {
box->closeBox();
});
}
}
} // namespace UrlAuthBox
@@ -20,6 +20,7 @@ struct Result {
bool auth : 1 = false;
bool allowWrite : 1 = false;
bool sharePhone : 1 = false;
QString matchCode;
};
class SwitchableUserpicButton final : public Ui::RippleButton {
@@ -69,6 +70,7 @@ void ShowDetails(
const QString &browser,
const QString &platform,
const QString &ip,
const QString &region);
const QString &region,
rpl::producer<QStringList> matchCodes = rpl::single(QStringList()));
} // namespace UrlAuthBox