mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add invite link QR code generation.
This commit is contained in:
parent
27681db7f6
commit
16b4959e71
7 changed files with 164 additions and 18 deletions
|
@ -1232,6 +1232,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_group_invite_context_copy" = "Copy";
|
"lng_group_invite_context_copy" = "Copy";
|
||||||
"lng_group_invite_context_share" = "Share";
|
"lng_group_invite_context_share" = "Share";
|
||||||
"lng_group_invite_context_edit" = "Edit";
|
"lng_group_invite_context_edit" = "Edit";
|
||||||
|
"lng_group_invite_context_qr" = "Get QR Code";
|
||||||
"lng_group_invite_context_revoke" = "Revoke";
|
"lng_group_invite_context_revoke" = "Revoke";
|
||||||
"lng_group_invite_context_delete" = "Delete";
|
"lng_group_invite_context_delete" = "Delete";
|
||||||
"lng_group_invite_context_delete_all" = "Delete all";
|
"lng_group_invite_context_delete_all" = "Delete all";
|
||||||
|
@ -1261,6 +1262,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_group_invite_used_about" = "This link reached its usage limit.";
|
"lng_group_invite_used_about" = "This link reached its usage limit.";
|
||||||
"lng_group_invite_can_join_via_link#one" = "{count} person can join via this link.";
|
"lng_group_invite_can_join_via_link#one" = "{count} person can join via this link.";
|
||||||
"lng_group_invite_can_join_via_link#other" = "{count} people can join via this link.";
|
"lng_group_invite_can_join_via_link#other" = "{count} people can join via this link.";
|
||||||
|
"lng_group_invite_qr_title" = "Invite by QR Code";
|
||||||
|
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
|
||||||
|
"lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
|
||||||
|
|
||||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||||
|
|
|
@ -38,17 +38,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
|
#include "qr/qr_generate.h"
|
||||||
|
#include "intro/intro_qr.h" // TelegramLogoImage
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
#include <QtGui/QGuiApplication>
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <QtCore/QMimeData>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kFirstPage = 20;
|
constexpr auto kFirstPage = 20;
|
||||||
constexpr auto kPerPage = 100;
|
constexpr auto kPerPage = 100;
|
||||||
|
constexpr auto kShareQrSize = 768;
|
||||||
|
constexpr auto kShareQrPadding = 16;
|
||||||
|
|
||||||
using LinkData = Api::InviteLink;
|
using LinkData = Api::InviteLink;
|
||||||
|
|
||||||
|
@ -113,6 +118,105 @@ private:
|
||||||
return updated.link.isEmpty() || (!revoked && updated.revoked);
|
return updated.link.isEmpty() || (!revoked && updated.revoked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage QrExact(const Qr::Data &data, int pixel) {
|
||||||
|
const auto image = [](int size) {
|
||||||
|
auto result = QImage(
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
QPainter p(&result);
|
||||||
|
const auto skip = size / 12;
|
||||||
|
const auto logoSize = size - 2 * skip;
|
||||||
|
p.drawImage(
|
||||||
|
skip,
|
||||||
|
skip,
|
||||||
|
Intro::details::TelegramLogoImage().scaled(
|
||||||
|
logoSize,
|
||||||
|
logoSize,
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
return Qr::ReplaceCenter(
|
||||||
|
Qr::Generate(data, pixel, st::windowFg->c),
|
||||||
|
image(Qr::ReplaceSize(data, pixel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Qr(const Qr::Data &data, int pixel, int max = 0) {
|
||||||
|
Expects(data.size > 0);
|
||||||
|
|
||||||
|
if (max > 0 && data.size * pixel > max) {
|
||||||
|
pixel = std::max(max / data.size, 1);
|
||||||
|
}
|
||||||
|
return QrExact(data, pixel * style::DevicePixelRatio());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage Qr(const QString &text, int pixel, int max) {
|
||||||
|
return Qr(Qr::Encode(text), pixel, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage QrForShare(const QString &text) {
|
||||||
|
const auto data = Qr::Encode(text);
|
||||||
|
const auto size = (kShareQrSize - 2 * kShareQrPadding);
|
||||||
|
const auto image = QrExact(data, size / data.size);
|
||||||
|
auto result = QImage(
|
||||||
|
kShareQrPadding * 2 + image.width(),
|
||||||
|
kShareQrPadding * 2 + image.height(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(Qt::white);
|
||||||
|
{
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
p.drawImage(kShareQrPadding, kShareQrPadding, image);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
const QString &link,
|
||||||
|
Fn<void(QImage)> share) {
|
||||||
|
box->setTitle(tr::lng_group_invite_qr_title());
|
||||||
|
|
||||||
|
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
const auto qr = Qr(
|
||||||
|
link,
|
||||||
|
st::inviteLinkQrPixel,
|
||||||
|
st::boxWidth - st::boxRowPadding.left() - st::boxRowPadding.right());
|
||||||
|
const auto size = qr.width() / style::DevicePixelRatio();
|
||||||
|
const auto height = st::inviteLinkQrSkip * 2 + size;
|
||||||
|
const auto container = box->addRow(
|
||||||
|
object_ptr<Ui::BoxContentDivider>(box, height),
|
||||||
|
st::inviteLinkQrMargin);
|
||||||
|
const auto button = Ui::CreateChild<Ui::AbstractButton>(container);
|
||||||
|
button->resize(size, size);
|
||||||
|
button->paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
QPainter(button).drawImage(QRect(0, 0, size, size), qr);
|
||||||
|
}, button->lifetime());
|
||||||
|
container->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
button->move((width - size) / 2, st::inviteLinkQrSkip);
|
||||||
|
}, button->lifetime());
|
||||||
|
button->setClickedCallback([=] {
|
||||||
|
share(QrForShare(link));
|
||||||
|
});
|
||||||
|
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
tr::lng_group_invite_qr_about(),
|
||||||
|
st::boxLabel),
|
||||||
|
st::inviteLinkQrValuePadding);
|
||||||
|
|
||||||
|
box->addButton(
|
||||||
|
tr::lng_group_invite_context_copy(),
|
||||||
|
[=] { share(QrForShare(link)); });
|
||||||
|
}
|
||||||
|
|
||||||
Controller::Controller(
|
Controller::Controller(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<UserData*> admin,
|
not_null<UserData*> admin,
|
||||||
|
@ -147,6 +251,9 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||||
const auto shareLink = crl::guard(weak, [=] {
|
const auto shareLink = crl::guard(weak, [=] {
|
||||||
ShareInviteLinkBox(_peer, link);
|
ShareInviteLinkBox(_peer, link);
|
||||||
});
|
});
|
||||||
|
const auto getLinkQr = crl::guard(weak, [=] {
|
||||||
|
InviteLinkQrBox(link);
|
||||||
|
});
|
||||||
const auto revokeLink = crl::guard(weak, [=] {
|
const auto revokeLink = crl::guard(weak, [=] {
|
||||||
RevokeLink(_peer, admin, link);
|
RevokeLink(_peer, admin, link);
|
||||||
});
|
});
|
||||||
|
@ -170,6 +277,9 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_invite_context_share(tr::now),
|
tr::lng_group_invite_context_share(tr::now),
|
||||||
shareLink);
|
shareLink);
|
||||||
|
result->addAction(
|
||||||
|
tr::lng_group_invite_context_qr(tr::now),
|
||||||
|
getLinkQr);
|
||||||
if (!admin->isBot()) {
|
if (!admin->isBot()) {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_invite_context_edit(tr::now),
|
tr::lng_group_invite_context_edit(tr::now),
|
||||||
|
@ -473,12 +583,15 @@ SingleRowController::SingleRowController(
|
||||||
|
|
||||||
void SingleRowController::prepare() {
|
void SingleRowController::prepare() {
|
||||||
auto row = std::make_unique<PeerListRow>(_peer);
|
auto row = std::make_unique<PeerListRow>(_peer);
|
||||||
|
|
||||||
const auto raw = row.get();
|
const auto raw = row.get();
|
||||||
std::move(
|
std::move(
|
||||||
_status
|
_status
|
||||||
) | rpl::start_with_next([=](const QString &status) {
|
) | rpl::start_with_next([=](const QString &status) {
|
||||||
raw->setCustomStatus(status);
|
raw->setCustomStatus(status);
|
||||||
|
delegate()->peerListUpdateRow(raw);
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
delegate()->peerListAppendRow(std::move(row));
|
delegate()->peerListAppendRow(std::move(row));
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
}
|
}
|
||||||
|
@ -561,6 +674,11 @@ void AddPermanentLinkBlock(
|
||||||
ShareInviteLinkBox(peer, current.link);
|
ShareInviteLinkBox(peer, current.link);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const auto getLinkQr = crl::guard(weak, [=] {
|
||||||
|
if (const auto current = value->current(); !current.link.isEmpty()) {
|
||||||
|
InviteLinkQrBox(current.link);
|
||||||
|
}
|
||||||
|
});
|
||||||
const auto revokeLink = crl::guard(weak, [=] {
|
const auto revokeLink = crl::guard(weak, [=] {
|
||||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||||
const auto done = crl::guard(weak, [=] {
|
const auto done = crl::guard(weak, [=] {
|
||||||
|
@ -595,6 +713,9 @@ void AddPermanentLinkBlock(
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_invite_context_share(tr::now),
|
tr::lng_group_invite_context_share(tr::now),
|
||||||
shareLink);
|
shareLink);
|
||||||
|
result->addAction(
|
||||||
|
tr::lng_group_invite_context_qr(tr::now),
|
||||||
|
getLinkQr);
|
||||||
if (!admin->isBot()) {
|
if (!admin->isBot()) {
|
||||||
result->addAction(
|
result->addAction(
|
||||||
tr::lng_group_invite_context_revoke(tr::now),
|
tr::lng_group_invite_context_revoke(tr::now),
|
||||||
|
@ -790,6 +911,16 @@ void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InviteLinkQrBox(const QString &link) {
|
||||||
|
Ui::show(Box(QrBox, link, [=](const QImage &image) {
|
||||||
|
auto mime = std::make_unique<QMimeData>();
|
||||||
|
mime->setImageData(image);
|
||||||
|
QGuiApplication::clipboard()->setMimeData(mime.release());
|
||||||
|
|
||||||
|
Ui::Toast::Show(tr::lng_group_invite_qr_copied(tr::now));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
void EditLink(
|
void EditLink(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const Api::InviteLink &data) {
|
const Api::InviteLink &data) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ void AddPermanentLinkBlock(
|
||||||
|
|
||||||
void CopyInviteLink(const QString &link);
|
void CopyInviteLink(const QString &link);
|
||||||
void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link);
|
void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link);
|
||||||
|
void InviteLinkQrBox(const QString &link);
|
||||||
void RevokeLink(
|
void RevokeLink(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<UserData*> admin,
|
not_null<UserData*> admin,
|
||||||
|
|
|
@ -572,6 +572,9 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||||
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
|
||||||
ShareInviteLinkBox(_peer, link);
|
ShareInviteLinkBox(_peer, link);
|
||||||
});
|
});
|
||||||
|
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
||||||
|
InviteLinkQrBox(link);
|
||||||
|
});
|
||||||
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
||||||
EditLink(_peer, data);
|
EditLink(_peer, data);
|
||||||
});
|
});
|
||||||
|
|
|
@ -945,6 +945,11 @@ inviteLinkThreeDotsSkip: 12px;
|
||||||
inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 4px);
|
inviteLinkRevokedTitlePadding: margins(22px, 16px, 10px, 4px);
|
||||||
inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px);
|
inviteLinkLimitMargin: margins(22px, 8px, 22px, 8px);
|
||||||
|
|
||||||
|
inviteLinkQrPixel: 8px;
|
||||||
|
inviteLinkQrSkip: 24px;
|
||||||
|
inviteLinkQrMargin: margins(0px, 0px, 0px, 13px);
|
||||||
|
inviteLinkQrValuePadding: margins(22px, 0px, 22px, 12px);
|
||||||
|
|
||||||
infoAboutGigagroup: FlatLabel(defaultFlatLabel) {
|
infoAboutGigagroup: FlatLabel(defaultFlatLabel) {
|
||||||
minWidth: 274px;
|
minWidth: 274px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,24 +32,6 @@ namespace Intro {
|
||||||
namespace details {
|
namespace details {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
[[nodiscard]] QImage TelegramLogoImage() {
|
|
||||||
const auto size = QSize(st::introQrCenterSize, st::introQrCenterSize);
|
|
||||||
auto result = QImage(
|
|
||||||
size * style::DevicePixelRatio(),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
result.fill(Qt::transparent);
|
|
||||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
|
||||||
{
|
|
||||||
auto p = QPainter(&result);
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
p.setBrush(st::activeButtonBg);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.drawEllipse(QRect(QPoint(), size));
|
|
||||||
st::introQrPlane.paintInCenter(p, QRect(QPoint(), size));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QImage TelegramQrExact(const Qr::Data &data, int pixel) {
|
[[nodiscard]] QImage TelegramQrExact(const Qr::Data &data, int pixel) {
|
||||||
return Qr::Generate(data, pixel, st::windowFg->c);
|
return Qr::Generate(data, pixel, st::windowFg->c);
|
||||||
}
|
}
|
||||||
|
@ -430,5 +412,23 @@ void QrWidget::cancelled() {
|
||||||
api().request(base::take(_requestId)).cancel();
|
api().request(base::take(_requestId)).cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage TelegramLogoImage() {
|
||||||
|
const auto size = QSize(st::introQrCenterSize, st::introQrCenterSize);
|
||||||
|
auto result = QImage(
|
||||||
|
size * style::DevicePixelRatio(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
result.fill(Qt::transparent);
|
||||||
|
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
{
|
||||||
|
auto p = QPainter(&result);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setBrush(st::activeButtonBg);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.drawEllipse(QRect(QPoint(), size));
|
||||||
|
st::introQrPlane.paintInCenter(p, QRect(QPoint(), size));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
} // namespace Intro
|
} // namespace Intro
|
||||||
|
|
|
@ -59,5 +59,7 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QImage TelegramLogoImage();
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
} // namespace Intro
|
} // namespace Intro
|
||||||
|
|
Loading…
Add table
Reference in a new issue