Compare commits

...

20 Commits

Author SHA1 Message Date
John Preston
dab107cf90 Version 5.5.3.
- Fix custom emoji sending.
- Add mention link QR code sharing.
- Fix sending files from network drives. (Windows)
2024-09-10 15:07:14 +04:00
23rd
d1d1aa3d21 Moved premium gradient in box for share of QR code to first place. 2024-09-10 13:51:23 +03:00
23rd
d0536cc31f Replaced boxes for QR code of invite links with box for share QR code. 2024-09-10 13:51:23 +03:00
23rd
c47f5e9995 Added ability to create box for share QR code without peer. 2024-09-10 13:51:23 +03:00
23rd
0916836ff9 Added ability to provide description and custom link to FillPeerQrBox. 2024-09-10 13:51:23 +03:00
John Preston
5dd9ff1062 Accept only CF_HDROP for uri-list mime.
Additional types lead to such problem: some programs, like JetBrains
IDEs, like PyCharm, copy "file://[ip-address]/file" as an URI in
uri-list, that way you can't paste it in the messenger, because it
will make a request to that address and reveal clients IP address.
2024-09-10 14:19:37 +04:00
John Preston
d1e3b9f15d Revert "Check for local URLs more strictly."
This reverts commit c3fda41224.
2024-09-10 14:19:37 +04:00
John Preston
ca3c179b75 Use full available width for text in some places.
Fixes #28384.
2024-09-10 14:19:37 +04:00
John Preston
c2d5924508 Fix build with MSVC. 2024-09-10 14:19:37 +04:00
John Preston
016d0395c3 Fix custom emoji sending. 2024-09-10 14:17:32 +04:00
23rd
925c9239bd Added ability to share QR code from channels. 2024-09-10 11:34:55 +03:00
23rd
fdcbe3cf3a Fixed width of username label with right button in profile section. 2024-09-10 11:08:42 +03:00
23rd
f6b9cc5ce1 Improved padding in box for share QR box on different scales. 2024-09-10 10:56:02 +03:00
23rd
8c915e6dc3 Renamed FillProfileQrBox to FillPeerQrBox. 2024-09-10 03:26:15 +03:00
23rd
1db426da2e Added ability to choose quality in box for share of QR code. 2024-09-10 03:26:15 +03:00
23rd
d9be363962 Added ability to hide userpic from box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
88daa37e34 Added ability to generate QR code independently from ui scale. 2024-09-10 02:00:26 +03:00
23rd
931fa01337 Added premium gradient to box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
8e1595eb29 Adapted display of longest usernames in box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
29e97232d8 Added initial ability to share QR code from user profile. 2024-09-10 02:00:26 +03:00
39 changed files with 1026 additions and 81 deletions

View File

@@ -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/peer_qr_box.cpp
ui/boxes/peer_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

View File

@@ -0,0 +1 @@
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -5546,6 +5546,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_contact_details_birthday" = "Birthday";
"lng_contact_details_organization" = "Organization";
"lng_qr_box_quality" = "Quality";
"lng_qr_box_quality1" = "Normal";
"lng_qr_box_quality2" = "High";
"lng_qr_box_quality3" = "Very High";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -31,6 +31,7 @@
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
</qresource>
<qresource prefix="/icons">
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.5.2.0" />
Version="5.5.3.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,0
FILEVERSION 5,5,3,0
PRODUCTVERSION 5,5,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "5.5.2.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.2.0"
VALUE "ProductVersion", "5.5.3.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,0
FILEVERSION 5,5,3,0
PRODUCTVERSION 5,5,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "5.5.2.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.2.0"
VALUE "ProductVersion", "5.5.3.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -1120,3 +1120,10 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
selectLinkFg: windowActiveTextFg;
}
}
profileQrFont: font(fsize bold);
profileQrCenterSize: 34px;
profileQrBackgroundRadius: 12px;
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);

View File

@@ -582,6 +582,7 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));
@@ -890,6 +891,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
};
const auto getLinkQr = [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/boxes/edit_invite_link_session.h"
#include "ui/boxes/peer_qr_box.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h"
#include "ui/controls/userpic_button.h"
@@ -64,8 +65,8 @@ namespace {
constexpr auto kFirstPage = 20;
constexpr auto kPerPage = 100;
constexpr auto kShareQrSize = 768;
constexpr auto kShareQrPadding = 16;
// constexpr auto kShareQrSize = 768;
// constexpr auto kShareQrPadding = 16;
using LinkData = Api::InviteLink;
@@ -282,6 +283,8 @@ private:
return updated.link.isEmpty() || (!revoked && updated.revoked);
}
#if 0
QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
const auto image = [](int size) {
auto result = QImage(
@@ -383,6 +386,8 @@ void QrBox(
box->addLeftButton(tr::lng_group_invite_context_copy(), copyCallback);
}
#endif
Controller::Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@@ -421,6 +426,7 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
_peer,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@@ -1253,6 +1259,7 @@ void AddPermanentLinkBlock(
const auto getLinkQr = crl::guard(weak, [=] {
if (const auto current = value->current(); !current.link.isEmpty()) {
show->showBox(InviteLinkQrBox(
peer,
current.link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@@ -1510,16 +1517,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
}
object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about) {
return Box(QrBox, link, std::move(title), std::move(about), [=](
const QImage &image,
std::shared_ptr<Ui::Show> show) {
auto mime = std::make_unique<QMimeData>();
mime->setImageData(image);
QGuiApplication::clipboard()->setMimeData(mime.release());
show->showToast(tr::lng_group_invite_qr_copied(tr::now));
return Box([=, t = std::move(title), a = std::move(about)](
not_null<Ui::GenericBox*> box) {
Ui::FillPeerQrBox(box, peer, link, std::move(a));
box->setTitle(std::move(t));
});
}

View File

@@ -50,6 +50,7 @@ void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
const QString &link,
const QString &copied = {});
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about);

View File

@@ -587,6 +587,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
}, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));

View File

@@ -32,7 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h"
#include "platform/platform_file_utilities.h"
#include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h"
@@ -72,7 +71,7 @@ constexpr auto kMaxMessageLength = 4096;
using Ui::SendFilesWay;
[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
return !urls.isEmpty() && ranges::all_of(urls, Core::UrlIsLocal);
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
}
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {

View File

@@ -7,10 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Core {
bool UrlIsLocal(const QUrl &url);
} // namespace Core
namespace Main {
class Session;
} // namespace Main
@@ -49,7 +45,7 @@ void ShowInFolder(const QString &filepath);
namespace internal {
inline QString UrlToLocalDefault(const QUrl &url) {
return Core::UrlIsLocal(url) ? url.toLocalFile() : QString();
return url.toLocalFile();
}
void UnsafeOpenUrlDefault(const QString &url);

View File

@@ -226,24 +226,13 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
if (data->hasImage()) {
return true;
} else if (const auto urls = ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, UrlIsLocal)) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
}
return false;
}
bool UrlIsLocal(const QUrl &url) {
if (!url.isLocalFile()) {
return false;
}
const auto result = url.toLocalFile();
if (result.startsWith("//")) {
return false;
}
return !result.isEmpty();
}
QString FileExtension(const QString &filepath) {
const auto reversed = ranges::views::reverse(filepath);
const auto last = ranges::find_first_of(reversed, ".\\/");

View File

@@ -68,7 +68,6 @@ struct MimeImageData {
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
[[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> data);
[[nodiscard]] bool UrlIsLocal(const QUrl &url);
enum class NameType : uchar {
Unknown,

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 5005002;
constexpr auto AppVersionStr = "5.5.2";
constexpr auto AppVersion = 5005003;
constexpr auto AppVersionStr = "5.5.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -5786,7 +5786,7 @@ bool HistoryWidget::canSendFiles(not_null<const QMimeData*> data) const {
} else if (data->hasImage()) {
return true;
} else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, Core::UrlIsLocal)) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
}

View File

@@ -931,6 +931,7 @@ void Document::draw(
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
.useFullWidth = true,
});
}
}

View File

@@ -280,6 +280,7 @@ void Game::draw(Painter &p, const PaintContext &context) const {
.selection = toDescriptionSelection(context.selection),
.elisionHeight = _descriptionLines * lineHeight,
.elisionRemoveFromEnd = endskip,
.useFullWidth = true,
});
tshift += _descriptionLines * lineHeight;
}

View File

@@ -245,6 +245,7 @@ void Invoice::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = toDescriptionSelection(context.selection),
.useFullWidth = true,
});
tshift += _descriptionHeight;
}

View File

@@ -1085,6 +1085,7 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
? (_descriptionLines * lineHeight)
: 0),
.elisionRemoveFromEnd = (_descriptionLines > 0) ? endskip : 0,
.useFullWidth = true,
});
tshift += (_descriptionLines > 0)
? (_descriptionLines * lineHeight)

View File

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

View File

@@ -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/peer_qr_box.h"
#include "ui/boxes/report_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/painter.h"
@@ -1034,6 +1035,22 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
result.text->setContextCopyText(contextCopyText);
return result;
};
const auto fitLabelToButton = [&](
not_null<Ui::RpWidget*> button,
not_null<Ui::FlatLabel*> label) {
const auto parent = label->parentWidget();
result->sizeValue() | rpl::start_with_next([=] {
const auto s = parent->size();
button->moveToRight(
0,
(s.height() - button->height()) / 2);
label->resizeToWidth(
s.width()
- label->geometry().left()
- st::lineWidth * 2
- button->width());
}, button->lifetime());
};
if (const auto user = _peer->asUser()) {
const auto controller = _controller->parentController();
if (user->session().supportMode()) {
@@ -1101,19 +1118,19 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
usernameLine.subtext->overrideLinkClickHandler(callback);
usernameLine.text->setContextMenuHook(hook);
usernameLine.subtext->setContextMenuHook(hook);
const auto usernameLabel = usernameLine.text;
if (user->isBot()) {
if (user) {
const auto copyUsername = Ui::CreateChild<Ui::IconButton>(
usernameLabel->parentWidget(),
st::infoProfileLabeledButtonCopy);
result->sizeValue(
) | rpl::start_with_next([=] {
const auto s = usernameLabel->parentWidget()->size();
copyUsername->moveToRight(
0,
(s.height() - copyUsername->height()) / 2);
}, copyUsername->lifetime());
usernameLine.text->parentWidget(),
user->isBot()
? st::infoProfileLabeledButtonCopy
: st::infoProfileLabeledButtonQr);
fitLabelToButton(copyUsername, usernameLine.text);
copyUsername->setClickedCallback([=] {
if (!user->isBot()) {
controller->show(
Box(Ui::FillPeerQrBox, user, std::nullopt, nullptr));
return false;
}
const auto link = user->session().createInternalLinkFull(
user->username());
if (!link.isEmpty()) {
@@ -1172,7 +1189,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
: text) + addToLink,
(addToLink.isEmpty() ? link.url : (text + addToLink)));
});
auto linkLine = addInfoOneLine(
const auto linkLine = addInfoOneLine(
(topicRootId
? tr::lng_info_link_label(Ui::Text::WithEntities)
: UsernamesSubtext(_peer, tr::lng_info_link_label())),
@@ -1185,6 +1202,17 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
addToLink);
linkLine.text->overrideLinkClickHandler(linkCallback);
linkLine.subtext->overrideLinkClickHandler(linkCallback);
{
const auto qr = Ui::CreateChild<Ui::IconButton>(
linkLine.text->parentWidget(),
st::infoProfileLabeledButtonQr);
fitLabelToButton(qr, linkLine.text);
qr->setClickedCallback([=, peer = _peer] {
controller->show(
Box(Ui::FillPeerQrBox, peer, std::nullopt, nullptr));
return false;
});
}
if (const auto channel = _topic ? nullptr : _peer->asChannel()) {
auto locationText = LocationValue(

View File

@@ -154,11 +154,11 @@ rpl::producer<TextWithEntities> PhoneOrHiddenValue(not_null<UserData*> user) {
}
rpl::producer<TextWithEntities> UsernameValue(
not_null<UserData*> user,
not_null<PeerData*> peer,
bool primary) {
return (primary
? PlainPrimaryUsernameValue(user)
: (PlainUsernameValue(user) | rpl::type_erased())
? PlainPrimaryUsernameValue(peer)
: (PlainUsernameValue(peer) | rpl::type_erased())
) | rpl::map([](QString &&username) {
return username.isEmpty()
? QString()

View File

@@ -57,7 +57,7 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
[[nodiscard]] rpl::producer<TextWithEntities> PhoneOrHiddenValue(
not_null<UserData*> user);
[[nodiscard]] rpl::producer<TextWithEntities> UsernameValue(
not_null<UserData*> user,
not_null<PeerData*> peer,
bool primary = false);
[[nodiscard]] rpl::producer<std::vector<TextWithEntities>> UsernamesValue(
not_null<PeerData*> peer);

View File

@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/mac/file_utilities_mac.h"
#include "base/platform/mac/base_utilities_mac.h"
#include "core/mime_type.h"
#include "lang/lang_keys.h"
#include "styles/style_window.h"
@@ -380,9 +379,6 @@ namespace Platform {
namespace File {
QString UrlToLocal(const QUrl &url) {
if (!Core::UrlIsLocal(url)) {
return QString();
}
auto result = url.toLocalFile();
if (result.startsWith(u"/.file/id="_q)) {
NSString *nsurl = [[[NSURL URLWithString: [NSString stringWithUTF8String: (u"file://"_q + result).toUtf8().constData()]] filePathURL] path];

View File

@@ -606,6 +606,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
}, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_chat_link_qr_title(),
tr::lng_chat_link_qr_about()));

View File

@@ -83,9 +83,10 @@ bool ValidatePhotoEditorMediaDragData(not_null<const QMimeData*> data) {
}
if (!urls.isEmpty()) {
using namespace Core;
const auto file = Platform::File::UrlToLocal(urls.front());
if (!file.isEmpty()) {
const auto url = urls.front();
if (url.isLocalFile()) {
using namespace Core;
const auto file = Platform::File::UrlToLocal(url);
const auto info = QFileInfo(file);
return FileIsImage(file, MimeTypeForFile(info).name())
&& QImageReader(file).canRead();
@@ -106,10 +107,10 @@ bool ValidateEditMediaDragData(
}
if (albumType == Ui::AlbumType::PhotoVideo && !urls.isEmpty()) {
using namespace Core;
const auto file = Platform::File::UrlToLocal(urls.front());
if (!file.isEmpty()) {
const auto info = QFileInfo(file);
const auto url = urls.front();
if (url.isLocalFile()) {
using namespace Core;
const auto info = QFileInfo(Platform::File::UrlToLocal(url));
return IsMimeAcceptedForPhotoVideoAlbum(MimeTypeForFile(info).name());
}
}
@@ -133,10 +134,10 @@ MimeDataState ComputeMimeDataState(const QMimeData *data) {
auto allAreSmallImages = true;
for (const auto &url : urls) {
const auto file = Platform::File::UrlToLocal(url);
if (file.isEmpty()) {
if (!url.isLocalFile()) {
return MimeDataState::None;
}
const auto file = Platform::File::UrlToLocal(url);
const auto info = QFileInfo(file);
if (info.isDir()) {
@@ -174,13 +175,13 @@ PreparedList PrepareMediaList(
auto locals = QStringList();
locals.reserve(files.size());
for (const auto &url : files) {
locals.push_back(Platform::File::UrlToLocal(url));
if (locals.back().isEmpty()) {
if (!url.isLocalFile()) {
return {
PreparedList::Error::NonLocalUrl,
url.toDisplayString()
};
}
locals.push_back(Platform::File::UrlToLocal(url));
}
return PrepareMediaList(locals, previewWidth, premium);
}

View File

@@ -0,0 +1,875 @@
/*
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/peer_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/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#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/widgets/continuous_sliders.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_credits.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 <QtCore/QMimeData>
#include <QtGui/QGuiApplication>
#include <QtSvg/QSvgRenderer>
namespace Ui {
namespace {
using Colors = std::vector<QColor>;
[[nodiscard]] QMargins NoPhotoBackgroundMargins() {
return QMargins(
st::profileQrBackgroundMargins.left(),
st::profileQrBackgroundMargins.left(),
st::profileQrBackgroundMargins.right(),
st::profileQrBackgroundMargins.bottom());
}
[[nodiscard]] QImage TelegramQr(const Qr::Data &data, int pixel, int max) {
Expects(data.size > 0);
constexpr auto kCenterRatio = 0.175;
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 centerRect = Rect(size)
- Margins((size.width() - (size.width() * kCenterRatio)) / 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]] QMargins RoundedMargins(
const QMargins &backgroundMargins,
int photoSize,
int textMaxHeight) {
return (textMaxHeight
? (backgroundMargins + QMargins(0, photoSize / 2, 0, textMaxHeight))
: photoSize
? backgroundMargins + QMargins(0, photoSize / 2, 0, photoSize / 2)
: Margins(backgroundMargins.left()));
}
void Paint(
QPainter &p,
const style::font &font,
const QString &text,
const Colors &backgroundColors,
const QMargins &backgroundMargins,
const QImage &qrImage,
const QRect &qrRect,
int qrMaxSize,
int qrPixel,
int radius,
int textMaxHeight,
int photoSize) {
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
const auto roundedRect = qrRect
+ RoundedMargins(backgroundMargins, photoSize, textMaxHeight);
p.drawRoundedRect(roundedRect, radius, radius);
if (!qrImage.isNull() && !backgroundColors.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;
auto back = Images::GenerateGradient(
qrRect.size(),
backgroundColors,
gradientRotation,
1. - (gradientRotationAdd / 45.));
p.drawImage(qrRect, 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);
if (textMaxHeight) {
// '@' + QString(32, 'W');
auto p = QPainter(&colored);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::black);
p.setFont(font);
auto option = QTextOption(style::al_center);
option.setWrapMode(QTextOption::WrapAnywhere);
p.drawText(Rect(coloredSize), text, option);
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
p.drawImage(0, -back.height() + textMaxHeight, back);
}
p.drawImage(qrRect, qrImage);
if (textMaxHeight) {
p.drawImage(
qrRect.x(),
rect::bottom(qrRect)
+ ((rect::bottom(roundedRect) - rect::bottom(qrRect))
- textMaxHeight) / 2,
colored);
}
}
}
not_null<Ui::RpWidget*> PrepareQrWidget(
not_null<Ui::VerticalLayout*> container,
not_null<Ui::RpWidget*> topWidget,
const style::font &font,
rpl::producer<bool> userpicToggled,
rpl::producer<QString> username,
rpl::producer<QString> links,
rpl::producer<Colors> bgs,
rpl::producer<QString> about) {
const auto divider = container->add(
object_ptr<Ui::BoxContentDivider>(container));
struct State final {
explicit State(Fn<void()> callback) : updating(callback) {
updating.start();
}
Ui::Animations::Basic updating;
QImage qrImage;
Colors backgroundColors;
QString text;
QMargins backgroundMargins;
int textWidth = 0;
int textMaxHeight = 0;
int photoSize = 0;
};
const auto result = Ui::CreateChild<Ui::RpWidget>(divider);
topWidget->setParent(result);
topWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto state = result->lifetime().make_state<State>(
[=] { result->update(); });
const auto qrMaxSize = st::boxWideWidth
- rect::m::sum::h(st::boxRowPadding)
- rect::m::sum::h(st::profileQrBackgroundMargins);
const auto aboutLabel = Ui::CreateChild<Ui::FlatLabel>(
divider,
st::creditsBoxAboutDivider);
rpl::combine(
std::move(userpicToggled),
std::move(username),
std::move(bgs),
std::move(links),
std::move(about),
rpl::single(rpl::empty) | rpl::then(style::PaletteChanged())
) | rpl::start_with_next([=](
bool userpicToggled,
const QString &username,
const Colors &backgroundColors,
const QString &link,
const QString &about,
const auto &) {
state->backgroundMargins = userpicToggled
? st::profileQrBackgroundMargins
: NoPhotoBackgroundMargins();
state->photoSize = userpicToggled
? st::defaultUserpicButton.photoSize
: 0;
state->backgroundColors = backgroundColors;
state->text = username.toUpper();
state->textWidth = font->width(state->text);
{
const auto remainder = qrMaxSize % st::introQrPixel;
const auto downTo = remainder
? qrMaxSize - remainder
: qrMaxSize;
state->qrImage = TelegramQr(
Qr::Encode(link.toUtf8(), Qr::Redundancy::Default),
st::introQrPixel,
downTo).scaled(
Size(qrMaxSize * style::DevicePixelRatio()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
const auto resultWidth = qrMaxSize
+ rect::m::sum::h(state->backgroundMargins);
{
aboutLabel->setText(about);
aboutLabel->resizeToWidth(resultWidth);
}
const auto qrWidth = state->qrImage.width()
/ style::DevicePixelRatio();
const auto lines = int(state->textWidth / qrWidth) + 1;
state->textMaxHeight = state->textWidth ? (font->height * lines) : 0;
const auto whiteMargins = RoundedMargins(
state->backgroundMargins,
state->photoSize,
state->textMaxHeight);
result->resize(
qrMaxSize + rect::m::sum::h(whiteMargins),
qrMaxSize
+ rect::m::sum::v(whiteMargins) // White.
+ rect::m::sum::v(st::profileQrBackgroundPadding) // Gray.
+ state->photoSize / 2
+ aboutLabel->height());
divider->resize(container->width(), result->height());
result->moveToLeft((container->width() - result->width()) / 2, 0);
topWidget->setVisible(userpicToggled);
topWidget->moveToLeft(0, -std::numeric_limits<int>::min());
topWidget->raise();
aboutLabel->raise();
aboutLabel->moveToLeft(
result->x(),
divider->height()
- aboutLabel->height()
- st::defaultBoxDividerLabelPadding.top());
}, container->lifetime());
result->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(result);
const auto size = (state->qrImage.size() / style::DevicePixelRatio());
const auto qrRect = Rect(
(result->width() - size.width()) / 2,
state->backgroundMargins.top() + state->photoSize / 2,
size);
p.translate(
0,
st::profileQrBackgroundPadding.top() + state->photoSize / 2);
Paint(
p,
font,
state->text,
state->backgroundColors,
state->backgroundMargins,
state->qrImage,
qrRect,
qrMaxSize,
st::introQrPixel,
st::profileQrBackgroundRadius,
state->textMaxHeight,
state->photoSize);
if (!state->photoSize) {
return;
}
const auto photoSize = state->photoSize;
const auto top = Ui::GrabWidget(
topWidget,
QRect(),
Qt::transparent).scaled(
Size(photoSize * style::DevicePixelRatio()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
p.drawPixmap((result->width() - photoSize) / 2, -photoSize / 2, top);
}, result->lifetime());
return result;
}
[[nodiscard]] Fn<void(int)> AddDotsToSlider(
not_null<Ui::ContinuousSlider*> slider,
const style::MediaSlider &st,
int count) {
const auto lineWidth = st::lineWidth;
const auto smallSize = Size(st.seekSize.height() - st.width);
auto smallDots = std::vector<not_null<Ui::RpWidget*>>();
smallDots.reserve(count - 1);
const auto paintSmall = [=](QPainter &p, const QBrush &brush) {
auto hq = PainterHighQualityEnabler(p);
auto pen = st::boxBg->p;
pen.setWidth(st.width);
p.setPen(pen);
p.setBrush(brush);
p.drawEllipse(Rect(smallSize) - Margins(lineWidth));
};
for (auto i = 0; i < count - 1; i++) {
smallDots.push_back(
Ui::CreateChild<Ui::RpWidget>(slider->parentWidget()));
const auto dot = smallDots.back();
dot->resize(smallSize);
dot->setAttribute(Qt::WA_TransparentForMouseEvents);
dot->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(dot);
const auto fg = (slider->value() > (i / float64(count - 1)))
? st.activeFg
: st.inactiveFg;
paintSmall(p, fg);
}, dot->lifetime());
}
const auto bigDot = Ui::CreateChild<Ui::RpWidget>(slider->parentWidget());
bigDot->resize(st.seekSize);
bigDot->setAttribute(Qt::WA_TransparentForMouseEvents);
bigDot->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(bigDot);
auto hq = PainterHighQualityEnabler(p);
auto pen = st::boxBg->p;
pen.setWidth(st.width);
p.setPen(pen);
p.setBrush(st.activeFg);
p.drawEllipse(Rect(st.seekSize) - Margins(lineWidth));
}, bigDot->lifetime());
return [=](int index) {
const auto g = slider->geometry();
const auto bigTop = g.y() + (g.height() - bigDot->height()) / 2;
const auto smallTop = g.y()
+ (g.height() - smallSize.height()) / 2;
for (auto i = 0; i < count; ++i) {
if (index == i) {
const auto x = ((g.width() - bigDot->width()) * i)
/ float64(count - 1);
bigDot->move(g.x() + std::ceil(x), bigTop);
} else {
const auto k = (i < index) ? i : i - 1;
const auto w = smallDots[k]->width();
smallDots[k]->move(
g.x() + ((g.width() - w) * i) / (count - 1),
smallTop);
}
}
};
}
} // namespace
void FillPeerQrBox(
not_null<Ui::GenericBox*> box,
PeerData *peer,
std::optional<QString> customLink,
rpl::producer<QString> about) {
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 {
Ui::RpWidget* saveButton = nullptr;
rpl::variable<bool> saveButtonBusy = false;
rpl::variable<bool> userpicToggled = true;
rpl::variable<Colors> bgs;
Ui::Animations::Simple animation;
rpl::variable<int> chosen = 0;
rpl::variable<int> scaleValue = 0;
style::font font;
};
const auto state = box->lifetime().make_state<State>();
state->userpicToggled = !(customLink || !peer);
const auto createFont = [=](int scale) {
return style::font(
style::ConvertScale(30, scale),
st::profileQrFont->flags(),
st::profileQrFont->family());
};
state->font = createFont(style::Scale());
const auto usernameValue = [=] {
return (customLink || !peer)
? (rpl::single(QString()) | rpl::type_erased())
: Info::Profile::UsernameValue(peer, true) | rpl::map(
[](const auto &username) { return username.text; });
};
const auto linkValue = [=] {
return customLink
? rpl::single(*customLink)
: peer
? Info::Profile::LinkValue(peer, true) | rpl::map(
[](const auto &link) { return link.text; })
: (rpl::single(QString()) | rpl::type_erased());
};
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
box,
peer ? peer : controller->session().user().get(),
st::defaultUserpicButton);
userpic->setVisible(peer != nullptr);
PrepareQrWidget(
box->verticalLayout(),
userpic,
state->font,
state->userpicToggled.value(),
usernameValue(),
linkValue(),
state->bgs.value(),
about ? std::move(about) : rpl::single(QString()));
Ui::AddSkip(box->verticalLayout());
Ui::AddSubsectionTitle(
box->verticalLayout(),
tr::lng_userpic_builder_color_subtitle());
const auto themesContainer = box->addRow(
object_ptr<Ui::VerticalLayout>(box));
const auto activewidth = int(
(st::defaultInputField.borderActive + st::lineWidth) * 0.9);
const auto size = st::chatThemePreviewSize.width();
const auto fill = [=](const std::vector<Data::CloudTheme> &cloudThemes) {
while (themesContainer->count()) {
delete themesContainer->widgetAt(0);
}
struct State {
Colors colors;
QImage image;
};
constexpr auto kMaxInRow = 4;
constexpr auto kMaxColors = 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);
auto colorsCollection = ranges::views::all(
cloudThemes
) | ranges::views::transform([](const auto &cloudTheme) -> Colors {
const auto it = cloudTheme.settings.find(
Data::CloudThemeType::Light);
if (it == end(cloudTheme.settings)) {
return Colors();
}
const auto colors = it->second.paper
? it->second.paper->backgroundColors()
: Colors();
if (colors.size() != kMaxColors) {
return Colors();
}
return colors;
}) | ranges::views::filter([](const Colors &colors) {
return !colors.empty();
}) | ranges::to_vector;
colorsCollection.insert(
colorsCollection.begin(),
Colors{
st::premiumButtonBg1->c,
st::premiumButtonBg1->c,
st::premiumButtonBg2->c,
st::premiumButtonBg3->c,
});
// colorsCollection.push_back(Colors{
// st::creditsBg1->c,
// st::creditsBg2->c,
// st::creditsBg1->c,
// st::creditsBg2->c,
// });
for (const auto &colors : colorsCollection) {
if (state->bgs.current().empty()) {
state->bgs = colors;
}
if (counter % kMaxInRow == 0) {
Ui::AddSkip(themesContainer);
row = themesContainer->add(
object_ptr<Ui::RpWidget>(themesContainer));
row->resize(size, size);
}
const auto widget = Ui::CreateChild<Ui::AbstractButton>(row);
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() == kMaxColors) {
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());
counter++;
}
Ui::AddSkip(themesContainer);
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());
}
Ui::AddSkip(box->verticalLayout());
Ui::AddDivider(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
Ui::AddSubsectionTitle(
box->verticalLayout(),
tr::lng_qr_box_quality());
Ui::AddSkip(box->verticalLayout());
constexpr auto kMaxQualities = 3;
{
const auto seekSize = st::settingsScale.seekSize.height();
const auto &labelSt = st::defaultFlatLabel;
const auto labels = box->verticalLayout()->add(
Ui::CreateSkipWidget(
box,
labelSt.style.font->height + labelSt.style.font->descent),
st::boxRowPadding);
const auto left = Ui::CreateChild<Ui::FlatLabel>(
labels,
tr::lng_qr_box_quality1(),
labelSt);
const auto middle = Ui::CreateChild<Ui::FlatLabel>(
labels,
tr::lng_qr_box_quality2(),
labelSt);
const auto right = Ui::CreateChild<Ui::FlatLabel>(
labels,
tr::lng_qr_box_quality3(),
labelSt);
labels->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
left->moveToLeft(0, 0);
middle->moveToLeft((size.width() - middle->width()) / 2, 0);
right->moveToRight(0, 0);
}, labels->lifetime());
const auto slider = box->verticalLayout()->add(
object_ptr<Ui::MediaSliderWheelless>(
box->verticalLayout(),
st::settingsScale),
st::boxRowPadding);
slider->resize(slider->width(), seekSize);
const auto active = st::windowActiveTextFg->c;
const auto inactive = st::windowSubTextFg->c;
const auto colorize = [=](int index) {
if (index == 0) {
left->setTextColorOverride(active);
middle->setTextColorOverride(inactive);
right->setTextColorOverride(inactive);
} else if (index == 1) {
left->setTextColorOverride(inactive);
middle->setTextColorOverride(active);
right->setTextColorOverride(inactive);
} else if (index == 2) {
left->setTextColorOverride(inactive);
middle->setTextColorOverride(inactive);
right->setTextColorOverride(active);
}
};
const auto updateGeometry = AddDotsToSlider(
slider,
st::settingsScale,
kMaxQualities);
slider->geometryValue(
) | rpl::start_with_next([=](const QRect &rect) {
updateGeometry(int(slider->value() * (kMaxQualities - 1)));
}, box->lifetime());
box->setShowFinishedCallback([=] {
colorize(0);
updateGeometry(0);
});
slider->setPseudoDiscrete(
kMaxQualities,
[=](int index) { return index; },
0,
[=](int scale) {
state->scaleValue = scale;
colorize(scale);
updateGeometry(scale);
},
[](int) {});
}
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
if (peer) {
const auto userpicToggle = box->verticalLayout()->add(
object_ptr<Ui::SettingsButton>(
box->verticalLayout(),
(peer->isUser()
? tr::lng_mediaview_profile_photo
: (peer->isChannel() && !peer->isMegagroup())
? tr::lng_mediaview_channel_photo
: tr::lng_mediaview_group_photo)(),
st::settingsButtonNoIcon));
userpicToggle->toggleOn(state->userpicToggled.value(), true);
userpicToggle->setClickedCallback([=] {
state->userpicToggled = !state->userpicToggled.current();
});
}
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
auto buttonText = rpl::conditional(
state->saveButtonBusy.value() | rpl::map(rpl::mappers::_1),
rpl::single(QString()),
tr::lng_chat_link_copy());
const auto show = controller->uiShow();
state->saveButton = box->addButton(std::move(buttonText), [=] {
if (state->saveButtonBusy.current()) {
return;
}
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
state->saveButtonBusy = true;
if (state->saveButton) {
state->saveButton->resizeToWidth(buttonWidth);
}
const auto userpicToggled = state->userpicToggled.current();
const auto scale = style::kScaleDefault
* (kMaxQualities + int(state->scaleValue.current() * 2));
const auto divider = std::max(1, style::Scale())
/ style::kScaleDefault;
const auto profileQrBackgroundRadius = style::ConvertScale(
st::profileQrBackgroundRadius / divider,
scale);
const auto introQrPixel = style::ConvertScale(
st::introQrPixel / divider,
scale);
const auto lineWidth = style::ConvertScale(
st::lineWidth / divider,
scale);
const auto boxWideWidth = style::ConvertScale(
st::boxWideWidth / divider,
scale);
const auto createMargins = [&](const style::margins &margins) {
return QMargins(
style::ConvertScale(margins.left() / divider, scale),
style::ConvertScale(margins.top() / divider, scale),
style::ConvertScale(margins.right() / divider, scale),
style::ConvertScale(margins.bottom() / divider, scale));
};
const auto boxRowPadding = createMargins(st::boxRowPadding);
const auto backgroundMargins = userpicToggled
? createMargins(st::profileQrBackgroundMargins)
: createMargins(NoPhotoBackgroundMargins());
const auto qrMaxSize = boxWideWidth
- rect::m::sum::h(boxRowPadding)
- rect::m::sum::h(backgroundMargins);
const auto photoSize = userpicToggled
? style::ConvertScale(
st::defaultUserpicButton.photoSize / divider,
scale)
: 0;
const auto font = createFont(scale);
const auto username = rpl::variable<QString>(
usernameValue()).current().toUpper();
const auto link = rpl::variable<QString>(linkValue());
const auto textWidth = font->width(username);
const auto top = Ui::GrabWidget(
userpic,
{},
Qt::transparent);
const auto weak = Ui::MakeWeak(box);
crl::async([=] {
const auto qrImage = TelegramQr(
Qr::Encode(
link.current().toUtf8(),
Qr::Redundancy::Default),
introQrPixel,
qrMaxSize);
const auto qrWidth = qrImage.width() / style::DevicePixelRatio();
const auto lines = int(textWidth / qrWidth) + 1;
const auto textMaxHeight = textWidth ? font->height * lines : 0;
const auto whiteMargins = RoundedMargins(
backgroundMargins,
photoSize,
textMaxHeight);
const auto resultSize = QSize(
qrMaxSize + rect::m::sum::h(whiteMargins),
qrMaxSize + rect::m::sum::v(whiteMargins) + photoSize / 2);
const auto qrImageSize = qrImage.size()
/ style::DevicePixelRatio();
const auto qrRect = Rect(
(resultSize.width() - qrImageSize.width()) / 2,
whiteMargins.top() + photoSize / 2,
qrImageSize);
auto image = QImage(
resultSize * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
image.setDevicePixelRatio(style::DevicePixelRatio());
{
auto p = QPainter(&image);
p.translate(0, lineWidth); // Bad.
Paint(
p,
font,
username,
state->bgs.current(),
backgroundMargins,
qrImage,
qrRect,
qrMaxSize,
introQrPixel,
profileQrBackgroundRadius,
textMaxHeight,
photoSize);
if (userpicToggled) {
p.drawPixmap(
(resultSize.width() - photoSize) / 2,
0,
top.scaled(
Size(photoSize * style::DevicePixelRatio()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
}
}
crl::on_main(weak, [=] {
state->saveButtonBusy = false;
auto mime = std::make_unique<QMimeData>();
mime->setImageData(std::move(image));
QGuiApplication::clipboard()->setMimeData(mime.release());
show->showToast(tr::lng_group_invite_qr_copied(tr::now));
});
});
});
if (const auto saveButton = state->saveButton) {
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
saveButton,
saveButton->height() / 2);
AddChildToWidgetCenter(saveButton, loadingAnimation);
loadingAnimation->showOn(state->saveButtonBusy.value());
}
const auto buttonWidth = box->width()
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
state->saveButton->widthValue() | rpl::filter([=] {
return (state->saveButton->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
state->saveButton->resizeToWidth(buttonWidth);
}, state->saveButton->lifetime());
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
}
} // namespace Ui

View File

@@ -0,0 +1,22 @@
/*
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 FillPeerQrBox(
not_null<Ui::GenericBox*> box,
PeerData *peer,
std::optional<QString> customLink,
rpl::producer<QString> about);
} // namespace Ui

View File

@@ -457,7 +457,7 @@ if customRunCommand:
stage('patches', """
git clone https://github.com/desktop-app/patches.git
cd patches
git checkout 8cd00d57c7
git checkout e66e768a5f
""")
stage('msys64', """

View File

@@ -1,7 +1,7 @@
AppVersion 5005002
AppVersion 5005003
AppVersionStrMajor 5.5
AppVersionStrSmall 5.5.2
AppVersionStr 5.5.2
AppVersionStrSmall 5.5.3
AppVersionStr 5.5.3
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 5.5.2
AppVersionOriginal 5.5.3

View File

@@ -1,3 +1,9 @@
5.5.3 (10.09.24)
- Fix custom emoji sending.
- Add mention link QR code sharing.
- Fix sending files from network drives. (Windows)
5.5.2 (09.09.24)
- Support two bottom buttons in web apps.