From 29e97232d8c50a76d10318def3954371b7944971 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 7 Sep 2024 00:33:11 +0300 Subject: [PATCH] Added initial ability to share QR code from user profile. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/icons/plane_white.svg | 1 + Telegram/Resources/icons/qr_mini.png | Bin 0 -> 641 bytes Telegram/Resources/icons/qr_mini@2x.png | Bin 0 -> 1106 bytes Telegram/Resources/icons/qr_mini@3x.png | Bin 0 -> 1106 bytes Telegram/Resources/qrc/telegram/telegram.qrc | 1 + Telegram/SourceFiles/boxes/boxes.style | 5 + Telegram/SourceFiles/info/info.style | 8 +- .../info/profile/info_profile_actions.cpp | 19 +- .../SourceFiles/ui/boxes/profile_qr_box.cpp | 413 ++++++++++++++++++ .../SourceFiles/ui/boxes/profile_qr_box.h | 20 + Telegram/lib_qr | 2 +- 12 files changed, 464 insertions(+), 7 deletions(-) create mode 100644 Telegram/Resources/icons/plane_white.svg create mode 100644 Telegram/Resources/icons/qr_mini.png create mode 100644 Telegram/Resources/icons/qr_mini@2x.png create mode 100644 Telegram/Resources/icons/qr_mini@3x.png create mode 100644 Telegram/SourceFiles/ui/boxes/profile_qr_box.cpp create mode 100644 Telegram/SourceFiles/ui/boxes/profile_qr_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a9e84983a..0e3f062fb 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1482,6 +1482,8 @@ PRIVATE support/support_templates.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h + ui/boxes/profile_qr_box.cpp + ui/boxes/profile_qr_box.h ui/chat/attach/attach_item_single_file_preview.cpp ui/chat/attach/attach_item_single_file_preview.h ui/chat/attach/attach_item_single_media_preview.cpp diff --git a/Telegram/Resources/icons/plane_white.svg b/Telegram/Resources/icons/plane_white.svg new file mode 100644 index 000000000..2a8761c70 --- /dev/null +++ b/Telegram/Resources/icons/plane_white.svg @@ -0,0 +1 @@ + diff --git a/Telegram/Resources/icons/qr_mini.png b/Telegram/Resources/icons/qr_mini.png new file mode 100644 index 0000000000000000000000000000000000000000..b26188bcd692064d337080fa7db3f5e2b1eca446 GIT binary patch literal 641 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgf?6s$hV~7Xu z-Kl%Mm;(h`&4soG9JywH#BYO-u+Ek*>b#TngC zxb58UFqLl6m#q=VC@A2sio7~YAf&qUfoYrAE1jE+PsGkxRKA z)*#*@=6MY{JULB;8+yZ%xAZRKnlN{3{-_xeywiZbzTY0~cpLo?eK4E2yQrH^%K>raef&!d6`m!opwt0bw(F&3dRPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFyh%hsRA>e5nLTI~K@i8^Qxh$$ zOi&c7R9d{oQi+OKilCqfRz?tmiiLuL0TB_zDuu08AV`o{#8QF@#B^yCw6cvzL9Bz%zIYlim5xe`aStZr|ReR;x-i1Jw*{=ozRNW#4MG8rV-G`;g5= zTx?Bbs$Q=@FX9pN6!s&?o+2K$CFDIaPRuu}aPww58bp2|?Uo4~w&;k0A!2K{NDhNK zUZIXd11XcdOKwPafMLiPOVpB!?1o&~ltY#Q**0AfTMB&6AX%6}Ga<=Ea>|w5QfCa= z^M3h=@&H-z!K^L8FCfP~+kUU@ni_kF;RzypU6-8zw~@V`LFeR|RFmZzQ_FlsQ34fi zkK(ri0*bbyOOnxo{j%~Iv&gY6q`99p;Om?c*2?&lTpORlm^dWGOdUWjfqw<42GHr1 z_-|IA@EXBs(e`f!K7}z^+3u9pO(dS0Dm(QEVEIyT_&Q# ziW%sl3y~Y-TVx4Yt;iM`MRLizKE#%BQnr)yDSb;0u{)a!UDeeLR5PGwfCpz?A7bL& zM-~xRW@v2J{fZ9Wk9=~(tle*MqZm%>eT@3ayWCD%31A#?1g{>kxaJQTPV(vFgyoQc z?SS*h;B8IBgqF-T2S5P%ZnKthG|@Tt>Y4djxP{eX1JECxms{hltYcU0 zb`7t7?X{)3h|DR>sk5%()fQGWzs#|PbM?F1g!k4S@FCUbV`Z^qWV2>fP zJ!CfLpnL%DqkRAo{&!>&{44lj+HxzEG7mPr+)Vh3v;HdK55|muPf$#f0&(a=z6Fl0 z){LSRs#o~9_{GsJcRDF@Zq2hN3H^w5O)gAa#@R$ zRP8TZKY8ctX4vVjwr~rJ{t}N9>8FcUqJCP!r<9BN5VNfEKdI|TnL)h3@1c>6>|K3;s5{u07*qoM6N<$f)o4qC;$Ke literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/qr_mini@3x.png b/Telegram/Resources/icons/qr_mini@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..46074860a3463c08d4ac0e6ae1389ba0c9cc1304 GIT binary patch literal 1106 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(<^v49!D1}U6i==L6{e2=G#V@L(# z+o*$iR}2K+9uieKvi8+C@l*C6cx9@a>%#swXzx8Z(_2vH2lF})kEc=7zHcggopRjR zPiB4j)r}@y#>REVl^Y))`FOHZJpF)V0oxo#C^fb5Y})3g7{`51?-Yu;Zf~?L49hxj z!sGtFv-gtSGncIPcqj5na#M=py-cfD*PP@%Cf^9ln=5Bz?1cC#A7P5-X2ry{jBt>woDd;YJNl26~T-uao=B=Cprl&`1! z?N9hJx!3GYQ_`$|vHCcl%Z&Nl9?v}M&pzkbIKT0%mpgC#X5EQ>o%5Y9d=;9hAtALd zEm&c*@6#U=NoTLMMjt!3Y(}5QmYefir)^w5>B^h?A@?WG`ZIa9%Oq=S=5r0k4|pt? zkDqktUMXg%udsbWwTH%<;PQ!wRsL4}zbTO>!;Z~Vo$@!m5PmnwONd zFG@aNIoG4D>Q?uujlv@Q+MOJy^|ngB=$pIX;L$4f3n2sCf$OW Q)u8FVdQ&MBb@0A79X!vFvP literal 0 HcmV?d00001 diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index c065c5fad..09f8b0fac 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -31,6 +31,7 @@ ../../art/topic_icons/gray.svg ../../art/topic_icons/general.svg ../../icons/info/edit/links_subscription.svg + ../../icons/plane_white.svg ../../icons/calls/hands.lottie diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 7fb0f669a..9650b6b88 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -1120,3 +1120,8 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) { selectLinkFg: windowActiveTextFg; } } + +profileQrCenterSize: 34px; +profileQrBackgroundRadius: 12px; +profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }}; +profileQrBackgroundSkip: 36px; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 2978d66c4..3be1bd88a 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -433,12 +433,16 @@ infoProfileSeparatorPadding: margins( infoProfileLabeledButtonCopy: IconButton(defaultIconButton) { width: 34px; height: 34px; - icon: icon {{ "menu/copy", infoIconFg }}; - iconOver: icon {{ "menu/copy", infoIconFg }}; + icon: icon {{ "menu/copy", windowBgActive }}; + iconOver: icon {{ "menu/copy", windowBgActive }}; rippleAreaPosition: point(0px, 0px); rippleAreaSize: 34px; ripple: defaultRippleAnimation; } +infoProfileLabeledButtonQr: IconButton(infoProfileLabeledButtonCopy) { + icon: icon {{ "menu/qr_code", windowBgActive }}; + iconOver: icon {{ "menu/qr_code", windowBgActive }}; +} infoIconInformation: icon {{ "info/info_information", infoIconFg }}; infoIconAddMember: icon {{ "info/info_add_member", infoIconFg }}; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index e79a645d6..8b219334b 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -51,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "menu/menu_mute.h" #include "support/support_helper.h" +#include "ui/boxes/profile_qr_box.h" #include "ui/boxes/report_box.h" #include "ui/controls/userpic_button.h" #include "ui/painter.h" @@ -1102,10 +1103,14 @@ object_ptr DetailsFiller::setupInfo() { usernameLine.text->setContextMenuHook(hook); usernameLine.subtext->setContextMenuHook(hook); const auto usernameLabel = usernameLine.text; - if (user->isBot()) { - const auto copyUsername = Ui::CreateChild( - usernameLabel->parentWidget(), - st::infoProfileLabeledButtonCopy); + if (user) { + const auto copyUsername = user->isBot() + ? Ui::CreateChild( + usernameLabel->parentWidget(), + st::infoProfileLabeledButtonCopy) + : Ui::CreateChild( + usernameLabel->parentWidget(), + st::infoProfileLabeledButtonQr); result->sizeValue( ) | rpl::start_with_next([=] { const auto s = usernameLabel->parentWidget()->size(); @@ -1114,6 +1119,12 @@ object_ptr DetailsFiller::setupInfo() { (s.height() - copyUsername->height()) / 2); }, copyUsername->lifetime()); copyUsername->setClickedCallback([=] { + if (!user->isBot()) { + controller->show(Box([=](not_null box) { + Ui::FillProfileQrBox(box, user); + })); + return false; + } const auto link = user->session().createInternalLinkFull( user->username()); if (!link.isEmpty()) { diff --git a/Telegram/SourceFiles/ui/boxes/profile_qr_box.cpp b/Telegram/SourceFiles/ui/boxes/profile_qr_box.cpp new file mode 100644 index 000000000..fbfa09605 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/profile_qr_box.cpp @@ -0,0 +1,413 @@ +/* +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 "ui/boxes/profile_qr_box.h" + +#include "core/application.h" +#include "data/data_cloud_themes.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "info/profile/info_profile_values.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "qr/qr_generate.h" +#include "ui/controls/userpic_button.h" +#include "ui/effects/animations.h" +#include "ui/image/image_prepare.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/ui_utility.h" +#include "ui/vertical_list.h" +#include "ui/widgets/box_content_divider.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_giveaway.h" +#include "styles/style_intro.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" +#include "styles/style_widgets.h" +#include "styles/style_window.h" + +#include +#include +#include + +namespace Ui { +namespace { + +using Colors = std::vector; + +[[nodiscard]] QImage TelegramQr(const Qr::Data &data, int pixel, int max) { + Expects(data.size > 0); + + if (max > 0 && data.size * pixel > max) { + pixel = std::max(max / data.size, 1); + } + auto qr = Qr::Generate( + data, + pixel * style::DevicePixelRatio(), + Qt::transparent, + Qt::white); + { + auto p = QPainter(&qr); + auto hq = PainterHighQualityEnabler(p); + auto svg = QSvgRenderer(u":/gui/plane_white.svg"_q); + const auto size = qr.rect().size(); + const auto centerWidth = st::profileQrCenterSize + * style::DevicePixelRatio(); + const auto centerRect = Rect(size) + - Margins((size.width() - centerWidth) / 2); + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + p.setCompositionMode(QPainter::CompositionMode_Clear); + p.drawEllipse(centerRect); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + svg.render(&p, centerRect); + } + return qr; +} + +[[nodiscard]] not_null PrepareQrWidget( + not_null container, + not_null topWidget, + rpl::producer codes, + rpl::producer links, + rpl::producer> bgs) { + const auto divider = container->add( + object_ptr(container)); + struct State final { + explicit State(Fn callback) : updating(callback) { + updating.start(); + } + + Ui::Animations::Basic updating; + QImage qr; + std::vector bgs; + rpl::variable code; + rpl::variable link; + }; + auto palettes = rpl::single(rpl::empty) | rpl::then( + style::PaletteChanged() + ); + const auto result = Ui::CreateChild(divider); + topWidget->setParent(result); + topWidget->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto state = result->lifetime().make_state( + [=] { result->update(); }); + state->code = rpl::variable(std::move(codes)); + state->link = rpl::variable(std::move(links)); + std::move( + bgs + ) | rpl::start_with_next([=](const std::vector &bgs) { + state->bgs = bgs; + }, container->lifetime()); + const auto font = st::mainMenuResetScaleFont; + const auto textMaxHeight = font->height * 3; + const auto qrMaxSize = st::boxWideWidth + - rect::m::sum::h(st::boxRowPadding) + - 2 * st::profileQrBackgroundSkip; + result->resize( + qrMaxSize + 2 * st::profileQrBackgroundSkip, + qrMaxSize + 2 * st::profileQrBackgroundSkip + textMaxHeight * 2); + rpl::combine( + state->link.value() | rpl::map([](const QString &code) { + return Qr::Encode(code.toUtf8(), Qr::Redundancy::Default); + }), + rpl::duplicate(palettes) + ) | rpl::map([=](const Qr::Data &code, const auto &) { + return TelegramQr(code, st::introQrPixel, qrMaxSize); + }) | rpl::start_with_next([=](QImage &&image) { + state->qr = std::move(image); + }, result->lifetime()); + result->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(result); + const auto usualSize = 41; + const auto pixel = std::clamp( + qrMaxSize / usualSize, + 1, + st::introQrPixel); + const auto size = (state->qr.size() / style::DevicePixelRatio()); + const auto radius = st::profileQrBackgroundRadius; + const auto skip = st::profileQrBackgroundSkip; + const auto qr = QRect( + (result->width() - size.width()) / 2, + skip * 3, + size.width(), + size.height()); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(Qt::white); + p.drawRoundedRect( + qr + QMargins(skip, skip + skip / 2, skip, skip + textMaxHeight), + radius, + radius); + if (!state->qr.isNull() && !state->bgs.empty()) { + constexpr auto kDuration = crl::time(10000); + const auto angle = (crl::now() % kDuration) + / float64(kDuration) * 360.0; + const auto gradientRotation = int(angle / 45.) * 45; + const auto gradientRotationAdd = angle - gradientRotation; + + const auto center = QPointF(rect::center(qr)); + const auto radius = std::sqrt(std::pow(qr.width() / 2., 2) + + std::pow(qr.height() / 2., 2)); + auto back = Images::GenerateGradient( + qr.size(), + state->bgs, + gradientRotation, + 1. - (gradientRotationAdd / 45.)); + p.drawImage(qr, back); + const auto coloredSize = QSize(back.width(), textMaxHeight); + auto colored = QImage( + coloredSize * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + colored.setDevicePixelRatio(style::DevicePixelRatio()); + colored.fill(Qt::transparent); + { + // '@' + QString(32, 'W'); + auto p = QPainter(&colored); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::red); + p.setFont(font); + auto option = QTextOption(style::al_center); + option.setWrapMode(QTextOption::WrapAnywhere); + p.drawText( + Rect(coloredSize), + state->code.current().text.toUpper(), + option); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.drawImage(0, -back.height() + textMaxHeight, back); + } + p.drawImage(qr, state->qr); + p.drawImage(qr.x(), qr.y() + qr.height() + skip / 2, colored); + } + }, result->lifetime()); + result->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + divider->resize(container->width(), size.height()); + result->moveToLeft((container->width() - size.width()) / 2, 0); + + const auto qrHeight = state->qr.height() / style::DevicePixelRatio(); + topWidget->moveToLeft( + (result->width() - topWidget->width()) / 2, + (st::profileQrBackgroundSkip + + st::profileQrBackgroundSkip / 2 + - topWidget->height() / 2)); + topWidget->raise(); + }, divider->lifetime()); + return result; +} + +} // namespace + +void FillProfileQrBox( + not_null box, + not_null peer) { + const auto window = Core::App().findWindow(box); + const auto controller = window ? window->sessionController() : nullptr; + if (!controller) { + return; + } + box->setStyle(st::giveawayGiftCodeBox); + box->setNoContentMargin(true); + box->setWidth(st::aboutWidth); + box->setTitle(tr::lng_group_invite_context_qr()); + box->verticalLayout()->resizeToWidth(box->width()); + struct State { + rpl::variable> bgs; + Ui::Animations::Simple animation; + rpl::variable chosen = 0; + }; + const auto state = box->lifetime().make_state(); + const auto qr = PrepareQrWidget( + box->verticalLayout(), + Ui::CreateChild( + box, + peer, + st::defaultUserpicButton), + Info::Profile::UsernameValue(peer->asUser()), + Info::Profile::LinkValue(peer) | rpl::map([](const auto &link) { + return link.url; + }), + state->bgs.value()); + const auto themesContainer = box->addRow( + object_ptr(box)); + + const auto activewidth = int( + (st::defaultInputField.borderActive + st::lineWidth) * 0.9); + const auto size = st::chatThemePreviewSize.width(); + + const auto fill = [=](const std::vector &cloudThemes) { + while (themesContainer->count()) { + delete themesContainer->widgetAt(0); + } + Ui::AddSkip(themesContainer); + Ui::AddSkip(themesContainer); + Ui::AddSkip(themesContainer); + Ui::AddSkip(themesContainer); + struct State { + Colors colors; + QImage image; + }; + constexpr auto kMaxInRow = 4; + auto row = (Ui::RpWidget*)(nullptr); + auto counter = 0; + const auto spacing = (0 + + (box->width() - rect::m::sum::h(st::boxRowPadding)) + - (kMaxInRow * size)) / (kMaxInRow + 1); + + for (const auto &cloudTheme : cloudThemes) { + const auto it = cloudTheme.settings.find( + Data::CloudThemeType::Light); + if (it == end(cloudTheme.settings)) { + continue; + } + const auto colors = it->second.paper + ? it->second.paper->backgroundColors() + : std::vector(); + if (colors.size() != 4) { + continue; + } + if (state->bgs.current().empty()) { + state->bgs = colors; + } + + if (counter % kMaxInRow == 0) { + row = themesContainer->add( + object_ptr(themesContainer)); + row->resize(size, size); + } + const auto widget = Ui::CreateChild(row); + const auto widgetState = widget->lifetime().make_state(); + widget->setClickedCallback([=] { + state->chosen = counter; + widget->update(); + state->animation.stop(); + state->animation.start([=](float64 value) { + const auto was = state->bgs.current(); + const auto now = colors; + if (was.size() == now.size(); was.size() == 4) { + state->bgs = Colors({ + anim::color(was[0], now[0], value), + anim::color(was[1], now[1], value), + anim::color(was[2], now[2], value), + anim::color(was[3], now[3], value), + }); + } + }, + 0., + 1., + st::shakeDuration); + }); + state->chosen.value() | rpl::combine_previous( + ) | rpl::filter([=](int i, int k) { + return i == counter || k == counter; + }) | rpl::start_with_next([=] { + widget->update(); + }, widget->lifetime()); + widget->resize(size, size); + widget->moveToLeft( + spacing + ((counter % kMaxInRow) * (size + spacing)), + 0); + widget->show(); + const auto back = [&] { + auto result = Images::Round( + Images::GenerateGradient( + Size(size - activewidth * 5), + colors, + 0, + 0), + ImageRoundRadius::Large); + auto colored = result; + colored.fill(Qt::transparent); + { + auto p = QPainter(&colored); + auto hq = PainterHighQualityEnabler(p); + st::profileQrIcon.paintInCenter(p, result.rect()); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.drawImage(0, 0, result); + } + auto temp = result; + temp.fill(Qt::transparent); + { + auto p = QPainter(&temp); + auto hq = PainterHighQualityEnabler(p); + p.setPen(st::premiumButtonFg); + p.setBrush(st::premiumButtonFg); + const auto size = st::profileQrIcon.width() * 1.5; + const auto margins = Margins((result.width() - size) / 2); + const auto inner = result.rect() - margins; + p.drawRoundedRect( + inner, + st::roundRadiusLarge, + st::roundRadiusLarge); + p.drawImage(0, 0, colored); + } + { + auto p = QPainter(&result); + p.drawImage(0, 0, temp); + } + return result; + }(); + widget->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(widget); + const auto rect = widget->rect() - Margins(activewidth * 2.5); + p.drawImage(rect.x(), rect.y(), back); + if (state->chosen.current() == counter) { + auto hq = PainterHighQualityEnabler(p); + auto pen = st::activeLineFg->p; + pen.setWidth(st::defaultInputField.borderActive); + p.setPen(pen); + p.drawRoundedRect( + widget->rect() - Margins(pen.width()), + st::roundRadiusLarge + activewidth * 4.2, + st::roundRadiusLarge + activewidth * 4.2); + } + }, widget->lifetime()); + if ((++counter) >= kMaxInRow) { + Ui::AddSkip(themesContainer); + } + } + themesContainer->resizeToWidth(box->width()); + }; + + const auto themes = &controller->session().data().cloudThemes(); + const auto &list = themes->chatThemes(); + if (!list.empty()) { + fill(list); + } else { + themes->refreshChatThemes(); + themes->chatThemesUpdated( + ) | rpl::take(1) | rpl::start_with_next([=] { + fill(themes->chatThemes()); + }, box->lifetime()); + } + + const auto show = controller->uiShow(); + const auto button = box->addButton(tr::lng_chat_link_copy(), [=] { + auto mime = std::make_unique(); + mime->setImageData(Ui::GrabWidget(qr, {}, Qt::transparent)); + QGuiApplication::clipboard()->setMimeData(mime.release()); + show->showToast(tr::lng_group_invite_qr_copied(tr::now)); + }); + const auto buttonWidth = box->width() + - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); + button->widthValue() | rpl::filter([=] { + return (button->widthNoMargins() != buttonWidth); + }) | rpl::start_with_next([=] { + button->resizeToWidth(buttonWidth); + }, button->lifetime()); + box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/profile_qr_box.h b/Telegram/SourceFiles/ui/boxes/profile_qr_box.h new file mode 100644 index 000000000..e942f2850 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/profile_qr_box.h @@ -0,0 +1,20 @@ +/* +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 +*/ +#pragma once + +class PeerData; + +namespace Ui { + +class GenericBox; + +void FillProfileQrBox( + not_null box, + not_null peer); + +} // namespace Ui diff --git a/Telegram/lib_qr b/Telegram/lib_qr index 501f4c350..6fdf60461 160000 --- a/Telegram/lib_qr +++ b/Telegram/lib_qr @@ -1 +1 @@ -Subproject commit 501f4c3502fd872ab4d777df8911bdac32de7c48 +Subproject commit 6fdf60461444ba150e13ac36009c0ffce72c4c83