mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 15:04:57 +00:00
598 lines
15 KiB
C++
598 lines
15 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "boxes/url_auth_box_content.h"
|
|
|
|
#include "base/qthelp_url.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "ui/dynamic_image.h"
|
|
#include "ui/effects/ripple_animation.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#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"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_menu_icons.h"
|
|
#include "styles/style_settings.h"
|
|
|
|
namespace UrlAuthBox {
|
|
namespace {
|
|
|
|
constexpr auto kEmojiAnimationActiveFor = crl::time(250);
|
|
|
|
void PrepareFullWidthRoundButton(
|
|
not_null<Ui::RoundButton*> button,
|
|
not_null<Ui::VerticalLayout*> content,
|
|
const style::margins &padding) {
|
|
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());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void ShowMatchCodesBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
Fn<std::shared_ptr<Ui::DynamicImage>(QString)> emojiImageFactory,
|
|
const QString &domain,
|
|
const QStringList &codes,
|
|
Fn<void(QString)> callback,
|
|
bool isApp) {
|
|
box->setWidth(st::boxWidth);
|
|
box->setStyle(st::urlAuthBox);
|
|
|
|
const auto content = box->verticalLayout();
|
|
|
|
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();
|
|
struct State {
|
|
std::shared_ptr<Ui::DynamicImage> image;
|
|
bool hovered = false;
|
|
crl::time lastFrameUpdate = 0;
|
|
};
|
|
const auto state = overlay->lifetime().make_state<State>();
|
|
const auto animationActive = [=] {
|
|
return state->lastFrameUpdate
|
|
&& (crl::now() - state->lastFrameUpdate
|
|
<= kEmojiAnimationActiveFor);
|
|
};
|
|
const auto refreshImage = [=] {
|
|
if (state->image) {
|
|
state->image->subscribeToUpdates(nullptr);
|
|
}
|
|
state->image = emojiImageFactory
|
|
? emojiImageFactory(code)
|
|
: nullptr;
|
|
if (state->image) {
|
|
state->image->subscribeToUpdates([=] {
|
|
state->lastFrameUpdate = crl::now();
|
|
overlay->update();
|
|
});
|
|
}
|
|
overlay->update();
|
|
};
|
|
refreshImage();
|
|
overlay->lifetime().add([=] {
|
|
if (state->image) {
|
|
state->image->subscribeToUpdates(nullptr);
|
|
}
|
|
});
|
|
button->events() | rpl::on_next([=](not_null<QEvent*> e) {
|
|
switch (e->type()) {
|
|
case QEvent::Enter:
|
|
if (!state->hovered) {
|
|
state->hovered = true;
|
|
if (!animationActive()) {
|
|
refreshImage();
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::Leave:
|
|
state->hovered = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}, overlay->lifetime());
|
|
button->sizeValue() | rpl::on_next([=](QSize size) {
|
|
overlay->resize(size);
|
|
}, overlay->lifetime());
|
|
overlay->paintOn([=](QPainter &p) {
|
|
if (state->image) {
|
|
const auto side = st::urlAuthCodesButton.height;
|
|
const auto frame = state->image->image(side);
|
|
const auto visible = frame.isNull()
|
|
? 0
|
|
: (frame.width() / frame.devicePixelRatio());
|
|
p.drawImage(
|
|
QPoint(
|
|
(overlay->width() - visible) / 2,
|
|
(overlay->height() - visible) / 2),
|
|
frame);
|
|
return;
|
|
}
|
|
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->setFullRadius(true);
|
|
button->setClickedCallback([=] {
|
|
callback(code);
|
|
box->closeBox();
|
|
});
|
|
buttons->add(button);
|
|
}
|
|
|
|
Ui::AddSkip(content);
|
|
|
|
const auto domainUrl = isApp ? QString() : qthelp::validate_url(domain);
|
|
if (!domainUrl.isEmpty() || isApp) {
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
content->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
content,
|
|
tr::lng_url_auth_login_title(
|
|
lt_domain,
|
|
rpl::single(isApp
|
|
? tr::bold(domain)
|
|
: 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();
|
|
});
|
|
}
|
|
}
|
|
|
|
SwitchableUserpicButton::SwitchableUserpicButton(
|
|
not_null<Ui::RpWidget*> parent,
|
|
int size)
|
|
: RippleButton(parent, st::defaultRippleAnimation)
|
|
, _size(size)
|
|
, _userpicSize(st::restoreUserpicIcon.photoSize)
|
|
, _skip((_size - _userpicSize) / 2) {
|
|
resize(_size, _size);
|
|
}
|
|
|
|
void SwitchableUserpicButton::setUserpic(not_null<Ui::RpWidget*> userpic) {
|
|
_userpic = userpic;
|
|
_userpic->setParent(this);
|
|
_userpic->moveToRight(_skip, _skip);
|
|
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
_userpic->show();
|
|
update();
|
|
}
|
|
|
|
void SwitchableUserpicButton::setExpanded(bool expanded) {
|
|
if (_expanded == expanded) {
|
|
return;
|
|
}
|
|
_expanded = expanded;
|
|
const auto w = _expanded
|
|
? (_size * 2.5 - _userpicSize)
|
|
: _size;
|
|
resize(w, _size);
|
|
if (_userpic) {
|
|
_userpic->moveToRight(_skip, _skip);
|
|
}
|
|
update();
|
|
}
|
|
|
|
void SwitchableUserpicButton::paintEvent(QPaintEvent *e) {
|
|
auto p = QPainter(this);
|
|
paintRipple(p, 0, 0);
|
|
|
|
if (!_expanded) {
|
|
return;
|
|
}
|
|
|
|
const auto arrowSize = st::lineWidth * 10;
|
|
const auto center = QPoint(_size / 2, height() / 2 + st::lineWidth * 3);
|
|
|
|
auto pen = QPen(st::windowSubTextFg);
|
|
pen.setWidthF(st::lineWidth * 1.5);
|
|
p.setPen(pen);
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
|
|
p.drawLine(center, center + QPoint(-arrowSize / 2, -arrowSize / 2));
|
|
p.drawLine(center, center + QPoint(arrowSize / 2, -arrowSize / 2));
|
|
}
|
|
|
|
QImage SwitchableUserpicButton::prepareRippleMask() const {
|
|
return _expanded
|
|
? Ui::RippleAnimation::RoundRectMask(size(), height() / 2)
|
|
: Ui::RippleAnimation::EllipseMask(size());
|
|
}
|
|
|
|
QPoint SwitchableUserpicButton::prepareRippleStartPosition() const {
|
|
return mapFromGlobal(QCursor::pos());
|
|
}
|
|
|
|
void AddAuthInfoRow(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
const QString &topText,
|
|
const QString &bottomText,
|
|
const QString &leftText,
|
|
const style::icon &icon) {
|
|
const auto row = container->add(
|
|
object_ptr<Ui::RpWidget>(container),
|
|
st::boxRowPadding);
|
|
|
|
const auto topLabel = Ui::CreateChild<Ui::FlatLabel>(
|
|
row,
|
|
topText,
|
|
st::urlAuthBoxRowTopLabel);
|
|
topLabel->setSelectable(true);
|
|
Ui::InstallTooltip(topLabel, [=] {
|
|
return (topLabel->textMaxWidth() > topLabel->width())
|
|
? topText
|
|
: QString();
|
|
});
|
|
const auto bottomLabel = Ui::CreateChild<Ui::FlatLabel>(
|
|
row,
|
|
bottomText,
|
|
st::urlAuthBoxRowBottomLabel);
|
|
bottomLabel->setSelectable(true);
|
|
Ui::InstallTooltip(bottomLabel, [=] {
|
|
return (bottomLabel->textMaxWidth() > bottomLabel->width())
|
|
? bottomText
|
|
: QString();
|
|
});
|
|
const auto leftLabel = Ui::CreateChild<Ui::FlatLabel>(
|
|
row,
|
|
leftText,
|
|
st::boxLabel);
|
|
|
|
rpl::combine(
|
|
row->widthValue(),
|
|
topLabel->sizeValue(),
|
|
bottomLabel->sizeValue()
|
|
) | rpl::on_next([=](int rowWidth, QSize topSize, QSize bottomSize) {
|
|
const auto totalHeight = topSize.height() + bottomSize.height();
|
|
row->resize(rowWidth, totalHeight);
|
|
|
|
const auto left = st::sessionValuePadding.left();
|
|
const auto availableWidth = rowWidth
|
|
- leftLabel->width()
|
|
- left
|
|
- st::defaultVerticalListSkip;
|
|
|
|
topLabel->resizeToNaturalWidth(availableWidth);
|
|
topLabel->moveToRight(0, 0);
|
|
bottomLabel->resizeToNaturalWidth(availableWidth);
|
|
bottomLabel->moveToRight(0, topSize.height());
|
|
|
|
leftLabel->moveToLeft(left, (totalHeight - leftLabel->height()) / 2);
|
|
}, row->lifetime());
|
|
|
|
{
|
|
const auto widget = Ui::CreateChild<Ui::RpWidget>(row);
|
|
widget->resize(icon.size());
|
|
|
|
rpl::combine(
|
|
row->widthValue(),
|
|
topLabel->sizeValue(),
|
|
bottomLabel->sizeValue()
|
|
) | rpl::on_next([=](int rowWidth, QSize topSize, QSize bottomSize) {
|
|
const auto totalHeight = topSize.height() + bottomSize.height();
|
|
widget->moveToLeft(0, (totalHeight - leftLabel->height()) / 2);
|
|
}, row->lifetime());
|
|
|
|
widget->paintRequest() | rpl::on_next([=, &icon] {
|
|
auto p = QPainter(widget);
|
|
icon.paintInCenter(p, widget->rect());
|
|
}, widget->lifetime());
|
|
}
|
|
}
|
|
|
|
void Show(
|
|
not_null<Ui::GenericBox*> box,
|
|
const QString &url,
|
|
const QString &domain,
|
|
const QString &selfName,
|
|
const QString &botName,
|
|
Fn<void(Result)> callback) {
|
|
box->setWidth(st::boxWidth);
|
|
|
|
box->addRow(
|
|
object_ptr<Ui::FlatLabel>(
|
|
box,
|
|
tr::lng_url_auth_open_confirm(tr::now, lt_link, url),
|
|
st::boxLabel),
|
|
st::boxPadding);
|
|
|
|
const auto addCheckbox = [&](const TextWithEntities &text) {
|
|
const auto checkbox = box->addRow(
|
|
object_ptr<Ui::Checkbox>(
|
|
box,
|
|
text,
|
|
true,
|
|
st::urlAuthCheckbox),
|
|
style::margins(
|
|
st::boxPadding.left(),
|
|
st::boxPadding.bottom(),
|
|
st::boxPadding.right(),
|
|
st::boxPadding.bottom()));
|
|
checkbox->setAllowTextLines();
|
|
return checkbox;
|
|
};
|
|
|
|
const auto auth = addCheckbox(
|
|
tr::lng_url_auth_login_option(
|
|
tr::now,
|
|
lt_domain,
|
|
tr::bold(domain),
|
|
lt_user,
|
|
tr::bold(selfName),
|
|
tr::marked));
|
|
|
|
const auto allow = !botName.isEmpty()
|
|
? addCheckbox(tr::lng_url_auth_allow_messages(
|
|
tr::now,
|
|
lt_bot,
|
|
tr::bold(botName),
|
|
tr::marked))
|
|
: nullptr;
|
|
|
|
if (allow) {
|
|
rpl::single(
|
|
auth->checked()
|
|
) | rpl::then(
|
|
auth->checkedChanges()
|
|
) | rpl::on_next([=](bool checked) {
|
|
if (!checked) {
|
|
allow->setChecked(false);
|
|
}
|
|
allow->setDisabled(!checked);
|
|
}, auth->lifetime());
|
|
}
|
|
|
|
box->addButton(tr::lng_open_link(), [=] {
|
|
const auto authed = auth->checked();
|
|
const auto allowed = (authed && allow && allow->checked());
|
|
callback({
|
|
.auth = authed,
|
|
.allowWrite = allowed,
|
|
});
|
|
});
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}
|
|
|
|
void ShowDetails(
|
|
not_null<Ui::GenericBox*> box,
|
|
const QString &url,
|
|
const QString &domain,
|
|
Fn<std::shared_ptr<Ui::DynamicImage>(QString)> emojiImageFactory,
|
|
Fn<void(Result)> callback,
|
|
object_ptr<Ui::RpWidget> userpicOwned,
|
|
rpl::producer<QString> botName,
|
|
const QString &browser,
|
|
const QString &platform,
|
|
const QString &ip,
|
|
const QString ®ion,
|
|
rpl::producer<QStringList> matchCodes,
|
|
bool isApp) {
|
|
box->setWidth(st::boxWidth);
|
|
box->setStyle(st::urlAuthBox);
|
|
|
|
const auto content = box->verticalLayout();
|
|
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
if (userpicOwned) {
|
|
const auto userpic = content->add(
|
|
std::move(userpicOwned),
|
|
st::boxRowPadding,
|
|
style::al_top);
|
|
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
}
|
|
|
|
const auto domainUrl = isApp ? QString() : qthelp::validate_url(domain);
|
|
const auto userpicButtonWidth = st::restoreUserpicIcon.photoSize;
|
|
const auto titlePadding = style::margins(
|
|
st::boxRowPadding.left(),
|
|
st::boxRowPadding.top(),
|
|
st::boxRowPadding.right() + userpicButtonWidth,
|
|
st::boxRowPadding.bottom());
|
|
content->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
content,
|
|
(isApp
|
|
? tr::lng_url_auth_login_title(
|
|
lt_domain,
|
|
rpl::single(tr::bold(domain)),
|
|
tr::marked)
|
|
: domainUrl.isEmpty()
|
|
? tr::lng_url_auth_login_button(tr::marked)
|
|
: tr::lng_url_auth_login_title(
|
|
lt_domain,
|
|
rpl::single(Ui::Text::Link(domain, domainUrl)),
|
|
tr::marked)),
|
|
st::boxTitle),
|
|
titlePadding,
|
|
style::al_top);
|
|
Ui::AddSkip(content);
|
|
|
|
content->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
content,
|
|
(isApp
|
|
? tr::lng_url_auth_app_access(tr::rich)
|
|
: tr::lng_url_auth_site_access(tr::rich)),
|
|
st::urlAuthCheckboxAbout),
|
|
st::boxRowPadding);
|
|
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
if (!platform.isEmpty() || !browser.isEmpty()) {
|
|
AddAuthInfoRow(
|
|
content,
|
|
platform,
|
|
browser,
|
|
tr::lng_url_auth_device_label(tr::now),
|
|
st::menuIconDevices);
|
|
}
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
|
|
if (!ip.isEmpty() || !region.isEmpty()) {
|
|
AddAuthInfoRow(
|
|
content,
|
|
ip,
|
|
region,
|
|
tr::lng_url_auth_ip_label(tr::now),
|
|
st::menuIconAddress);
|
|
}
|
|
Ui::AddSkip(content);
|
|
Ui::AddSkip(content);
|
|
|
|
Ui::AddDividerText(
|
|
content,
|
|
rpl::single(tr::lng_url_auth_login_attempt(tr::now)));
|
|
Ui::AddSkip(content);
|
|
|
|
auto allowMessages = (Ui::SettingsButton*)(nullptr);
|
|
if (botName) {
|
|
allowMessages = content->add(
|
|
object_ptr<Ui::SettingsButton>(
|
|
content,
|
|
tr::lng_url_auth_allow_messages_label()));
|
|
allowMessages->toggleOn(rpl::single(false));
|
|
Ui::AddSkip(content);
|
|
Ui::AddDividerText(
|
|
content,
|
|
tr::lng_url_auth_allow_messages_about(
|
|
lt_bot,
|
|
std::move(botName)));
|
|
Ui::AddSkip(content);
|
|
}
|
|
|
|
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());
|
|
|
|
Ui::AddSkip(content);
|
|
{
|
|
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,
|
|
emojiImageFactory,
|
|
domain,
|
|
state->matchCodes,
|
|
[=](QString matchCode) {
|
|
callback({
|
|
.auth = true,
|
|
.allowWrite = (allowMessages
|
|
&& allowMessages->toggled()),
|
|
.matchCode = std::move(matchCode),
|
|
});
|
|
},
|
|
isApp);
|
|
}));
|
|
});
|
|
}
|
|
Ui::AddSkip(content);
|
|
{
|
|
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
|