Compare commits

..

40 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
John Preston
f05191e668 Version 5.5.2: Fix build. 2024-09-09 21:30:15 +04:00
John Preston
1ba189e59d Version 5.5.2.
- Support two bottom buttons in web apps.
- Fix text layout outside of the message bubble glitch.
- Don't stop video when dragging media viewer in window mode.
2024-09-09 21:12:33 +04:00
23rd
c40ca70aa6 Added date of admin promotional to EditAdminBox. 2024-09-09 17:20:52 +03:00
23rd
521f991167 Replaced float labels with text in dividers in EditAdminBox. 2024-09-09 17:20:52 +03:00
23rd
edf1417bbb Added date of restriction to EditRestrictedBox. 2024-09-09 17:20:52 +03:00
23rd
686e9643ad Replaced channel participant parsing with modern way in admin log. 2024-09-09 17:20:52 +03:00
23rd
975460d268 Added api support for dates of chat participant data. 2024-09-09 17:20:52 +03:00
23rd
6b0c606d25 Switched type of subscription date from Qt to TimeId. 2024-09-09 17:20:52 +03:00
23rd
93ff0bdcff Slightly improved style of box for sending GIF with caption. 2024-09-09 17:20:52 +03:00
23rd
bf9d90ca4e Fixed emoji display in header of link for dialog filters. 2024-09-09 17:20:52 +03:00
23rd
d6e5e1e8f7 Added handle of flood errors in request of join to chatlist. 2024-09-09 17:20:52 +03:00
23rd
d3ae2ef9ea Synced local English phrases with server. 2024-09-09 17:20:52 +03:00
John Preston
40b0854704 Don't pause video on viewer drag. 2024-09-09 17:54:16 +04:00
John Preston
9aec6b6496 Set min width for field collapsed quotes. 2024-09-09 17:24:15 +04:00
John Preston
d01f977960 Don't offer hello-sticker to user you've blocked. 2024-09-09 16:42:02 +04:00
John Preston
1444009ee2 Update submodules. 2024-09-09 14:58:40 +04:00
John Preston
14ee9bee26 Fix quotes starting with custom urls. 2024-09-09 14:58:40 +04:00
Ilya Fedin
93587ddc3c Allow scale preview in separate window on Wayland 2024-09-09 14:49:44 +04:00
John Preston
0c4d03477e Fix text layout in some cases. 2024-09-09 14:28:37 +04:00
John Preston
a35092f012 Support second button in web apps. 2024-09-09 13:29:00 +04:00
68 changed files with 2450 additions and 1137 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

File diff suppressed because it is too large Load diff

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.1.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,1,0
PRODUCTVERSION 5,5,1,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.1.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.1.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,1,0
PRODUCTVERSION 5,5,1,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.1.0"
VALUE "FileVersion", "5.5.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.1.0"
VALUE "ProductVersion", "5.5.3.0"
END
END
BLOCK "VarFileInfo"

View file

@ -232,12 +232,12 @@ void ImportInvite(
api->request(MTPchatlists_JoinChatlistInvite(
MTP_string(slug),
MTP_vector<MTPInputPeer>(std::move(inputs))
)).done(callback).fail(error).send();
)).done(callback).fail(error).handleFloodErrors().send();
} else {
api->request(MTPchatlists_JoinChatlistUpdates(
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
MTP_vector<MTPInputPeer>(std::move(inputs))
)).done(callback).fail(error).send();
)).done(callback).fail(error).handleFloodErrors().send();
}
}
@ -517,6 +517,8 @@ void ShowImportError(
} else {
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
? tr::lng_group_invite_bad_link(tr::now)
: error.startsWith(u"FLOOD_WAIT_"_q)
? tr::lng_flood_error(tr::now)
: error);
}
}

View file

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "boxes/add_contact_box.h" // ShowAddParticipantsError
#include "boxes/peers/add_participants_box.h" // ChatInviteForbidden
#include "data/data_changes.h"
@ -265,22 +264,24 @@ ChatParticipant::ChatParticipant(
_rank = qs(data.vrank().value_or_empty());
_rights = ChatAdminRightsInfo(data.vadmin_rights());
_by = peerToUser(peerFromUser(data.vpromoted_by()));
_date = data.vdate().v;
}, [&](const MTPDchannelParticipantSelf &data) {
_type = Type::Member;
_date = data.vdate().v;
_by = peerToUser(peerFromUser(data.vinviter_id()));
if (data.vsubscription_until_date()) {
_subscriptionDate = base::unixtime::parse(
data.vsubscription_until_date()->v);
_subscriptionDate = data.vsubscription_until_date()->v;
}
}, [&](const MTPDchannelParticipant &data) {
_type = Type::Member;
_date = data.vdate().v;
if (data.vsubscription_until_date()) {
_subscriptionDate = base::unixtime::parse(
data.vsubscription_until_date()->v);
_subscriptionDate = data.vsubscription_until_date()->v;
}
}, [&](const MTPDchannelParticipantBanned &data) {
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
_by = peerToUser(peerFromUser(data.vkicked_by()));
_date = data.vdate().v;
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
? Type::Banned
@ -357,10 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
return _rights;
}
QDateTime ChatParticipant::subscriptionDate() const {
TimeId ChatParticipant::subscriptionDate() const {
return _subscriptionDate;
}
TimeId ChatParticipant::promotedSince() const {
return (_type == Type::Admin) ? _date : TimeId(0);
}
TimeId ChatParticipant::restrictedSince() const {
return (_type == Type::Restricted || _type == Type::Banned)
? _date
: TimeId(0);
}
TimeId ChatParticipant::memberSince() const {
return (_type == Type::Member) ? _date : TimeId(0);
}
ChatParticipant::Type ChatParticipant::type() const {
return _type;
}

View file

@ -60,7 +60,10 @@ public:
ChatRestrictionsInfo restrictions() const;
ChatAdminRightsInfo rights() const;
QDateTime subscriptionDate() const;
TimeId subscriptionDate() const;
TimeId promotedSince() const;
TimeId restrictedSince() const;
TimeId memberSince() const;
Type type() const;
QString rank() const;
@ -75,7 +78,8 @@ private:
bool _canBeEdited = false;
QString _rank;
QDateTime _subscriptionDate;
TimeId _subscriptionDate = 0;
TimeId _date = 0;
ChatRestrictionsInfo _restrictions;
ChatAdminRightsInfo _rights;

View file

@ -217,7 +217,9 @@ void ShowAddParticipantsError(
channel,
user,
ChatAdminRightsInfo(),
QString());
QString(),
0,
nullptr);
box->setSaveCallback(saveCallback);
*weak = box.data();
show->showBox(std::move(box));

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()));
@ -1114,7 +1116,7 @@ QString FilterChatStatusText(not_null<PeerData*> peer) {
? tr::lng_chat_status_subscribers
: tr::lng_chat_status_members)(
tr::now,
lt_count,
lt_count_decimal,
channel->membersCount());
}
}

View file

@ -170,11 +170,15 @@ void AddBotToGroupBoxController::requestExistingRights(
channel);
_existingRights = participant.rights().flags;
_existingRank = participant.rank();
_promotedSince = participant.promotedSince();
_promotedBy = participant.by();
addBotToGroup(_existingRightsChannel);
});
}).fail([=] {
_existingRights = ChatAdminRights();
_existingRank = QString();
_promotedSince = 0;
_promotedBy = 0;
addBotToGroup(_existingRightsChannel);
}).send();
}
@ -191,6 +195,8 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
_existingRights = {};
_existingRank = QString();
_existingRightsChannel = nullptr;
_promotedSince = 0;
_promotedBy = 0;
_bot->session().api().request(_existingRightsRequestId).cancel();
}
const auto requestedAddAdmin = (_scope == Scope::GroupAdmin)
@ -241,9 +247,12 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
bot,
ChatAdminRightsInfo(rights),
_existingRank,
_promotedSince,
_promotedBy ? chat->owner().user(_promotedBy).get() : nullptr,
EditAdminBotFields{
_token,
_existingRights.value_or(ChatAdminRights()) });
_existingRights.value_or(ChatAdminRights()),
});
box->setSaveCallback(saveCallback);
controller->show(std::move(box));
} else {

View file

@ -65,6 +65,8 @@ private:
mtpRequestId _existingRightsRequestId = 0;
std::optional<ChatAdminRights> _existingRights;
QString _existingRank;
TimeId _promotedSince = 0;
UserId _promotedBy = 0;
rpl::event_stream<not_null<PeerData*>> _groups;
rpl::event_stream<not_null<PeerData*>> _channels;

View file

@ -1276,7 +1276,9 @@ void AddSpecialBoxController::showAdmin(
_peer,
user,
currentRights,
_additional.adminRank(user));
_additional.adminRank(user),
_additional.adminPromotedSince(user),
_additional.adminPromotedBy(user));
const auto show = delegate()->peerListUiShow();
if (_additional.canAddOrEditAdmin(user)) {
const auto done = crl::guard(this, [=](
@ -1354,7 +1356,9 @@ void AddSpecialBoxController::showRestricted(
_peer,
user,
_additional.adminRights(user).has_value(),
currentRights);
currentRights,
_additional.restrictedBy(user),
_additional.restrictedSince(user));
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
ChatRestrictionsInfo newRights) {

View file

@ -206,6 +206,8 @@ EditAdminBox::EditAdminBox(
not_null<UserData*> user,
ChatAdminRightsInfo rights,
const QString &rank,
TimeId promotedSince,
UserData *by,
std::optional<EditAdminBotFields> addingBot)
: EditParticipantBox(
nullptr,
@ -214,6 +216,8 @@ EditAdminBox::EditAdminBox(
(rights.flags != 0))
, _oldRights(rights)
, _oldRank(rank)
, _promotedSince(promotedSince)
, _by(by)
, _addingBot(std::move(addingBot)) {
}
@ -288,8 +292,26 @@ void EditAdminBox::prepare() {
object_ptr<Ui::VerticalLayout>(this)));
const auto inner = _adminControlsWrap->entity();
Ui::AddDivider(inner);
Ui::AddSkip(inner);
if (_promotedSince) {
const auto parsed = base::unixtime::parse(_promotedSince);
const auto label = Ui::AddDividerText(
inner,
tr::lng_rights_about_by(
lt_user,
rpl::single(_by
? Ui::Text::Link(_by->name(), 1)
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
lt_date,
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
Ui::Text::WithEntities));
if (_by) {
label->setLink(1, _by->createOpenLink());
}
Ui::AddSkip(inner);
} else {
Ui::AddDivider(inner);
Ui::AddSkip(inner);
}
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
@ -356,17 +378,47 @@ void EditAdminBox::prepare() {
) | rpl::then(std::move(
changes
));
_aboutAddAdmins = inner->add(
object_ptr<Ui::FlatLabel>(inner, st::boxDividerLabel),
st::rightsAboutMargin);
rpl::duplicate(
selectedFlags
) | rpl::map(
(_1 & Flag::AddAdmins) != 0
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool checked) {
refreshAboutAddAdminsText(checked);
}, lifetime());
const auto hasRank = canSave() && (chat || channel->isMegagroup());
{
const auto aboutAddAdminsInner = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
const auto emptyAboutAddAdminsInner = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
aboutAddAdminsInner->toggle(false, anim::type::instant);
emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
if (hasRank) {
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
}
Ui::AddSkip(aboutAddAdminsInner->entity());
Ui::AddDividerText(
aboutAddAdminsInner->entity(),
rpl::duplicate(
selectedFlags
) | rpl::map(
(_1 & Flag::AddAdmins) != 0
) | rpl::distinct_until_changed(
) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
const auto empty = (amCreator() && user()->isSelf());
aboutAddAdminsInner->toggle(!empty, anim::type::instant);
emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
if (empty) {
return rpl::single(QString());
} else if (!canSave()) {
return tr::lng_rights_about_admin_cant_edit();
} else if (canAddAdmins) {
return tr::lng_rights_about_add_admins_yes();
}
return tr::lng_rights_about_add_admins_no();
}) | rpl::flatten_latest());
}
if (canTransferOwnership()) {
const auto allFlags = AdminRightsForOwnershipTransfer(options);
@ -381,9 +433,7 @@ void EditAdminBox::prepare() {
}
if (canSave()) {
_rank = (chat || channel->isMegagroup())
? addRankInput(inner).get()
: nullptr;
_rank = hasRank ? addRankInput(inner).get() : nullptr;
_finishSave = [=, value = getChecked] {
const auto newFlags = (value() | ChatAdminRight::Other)
& ((!channel || channel->amCreator())
@ -449,7 +499,7 @@ void EditAdminBox::refreshButtons() {
not_null<Ui::InputField*> EditAdminBox::addRankInput(
not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container);
// Ui::AddDivider(container);
container->add(
object_ptr<Ui::FlatLabel>(
@ -486,14 +536,13 @@ not_null<Ui::InputField*> EditAdminBox::addRankInput(
}
}, result->lifetime());
container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_rights_edit_admin_rank_about(
lt_title,
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()),
st::boxDividerLabel),
st::rightsAboutMargin);
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_rights_edit_admin_rank_about(
lt_title,
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
Ui::AddSkip(container);
return result;
}
@ -687,27 +736,18 @@ void EditAdminBox::sendTransferRequestFrom(
})).handleFloodErrors().send();
}
void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) {
_aboutAddAdmins->setText([&] {
if (amCreator() && user()->isSelf()) {
return QString();
} else if (!canSave()) {
return tr::lng_rights_about_admin_cant_edit(tr::now);
} else if (canAddAdmins) {
return tr::lng_rights_about_add_admins_yes(tr::now);
}
return tr::lng_rights_about_add_admins_no(tr::now);
}());
}
EditRestrictedBox::EditRestrictedBox(
QWidget*,
not_null<PeerData*> peer,
not_null<UserData*> user,
bool hasAdminRights,
ChatRestrictionsInfo rights)
ChatRestrictionsInfo rights,
UserData *by,
TimeId since)
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
, _oldRights(rights) {
, _oldRights(rights)
, _by(by)
, _since(since) {
}
void EditRestrictedBox::prepare() {
@ -782,6 +822,29 @@ void EditRestrictedBox::prepare() {
// tr::lng_rights_chat_banned_block(tr::now),
// st::boxLinkButton));
if (_since) {
const auto parsed = base::unixtime::parse(_since);
const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
const auto isBanned = (_oldRights.flags
& ChatRestriction::ViewMessages);
Ui::AddSkip(inner);
const auto label = Ui::AddDividerText(
inner,
(isBanned
? tr::lng_rights_chat_banned_by
: tr::lng_rights_chat_restricted_by)(
lt_user,
rpl::single(_by
? Ui::Text::Link(_by->name(), 1)
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
lt_date,
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
Ui::Text::WithEntities));
if (_by) {
label->setLink(1, _by->createOpenLink());
}
}
if (canSave()) {
const auto save = [=, value = getRestrictions] {
if (!_saveCallback) {

View file

@ -79,6 +79,8 @@ public:
not_null<UserData*> user,
ChatAdminRightsInfo rights,
const QString &rank,
TimeId promotedSince,
UserData *by,
std::optional<EditAdminBotFields> addingBot = {});
void setSaveCallback(
@ -110,7 +112,6 @@ private:
}
void finishAddAdmin();
void refreshButtons();
void refreshAboutAddAdminsText(bool canAddAdmins);
bool canTransferOwnership() const;
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
not_null<Ui::VerticalLayout*> container,
@ -127,11 +128,12 @@ private:
Ui::Checkbox *_addAsAdmin = nullptr;
Ui::SlideWrap<Ui::VerticalLayout> *_adminControlsWrap = nullptr;
Ui::InputField *_rank = nullptr;
QPointer<Ui::FlatLabel> _aboutAddAdmins;
mtpRequestId _checkTransferRequestId = 0;
mtpRequestId _transferRequestId = 0;
Fn<void()> _save, _finishSave;
TimeId _promotedSince = 0;
UserData *_by = nullptr;
std::optional<EditAdminBotFields> _addingBot;
};
@ -146,7 +148,9 @@ public:
not_null<PeerData*> peer,
not_null<UserData*> user,
bool hasAdminRights,
ChatRestrictionsInfo rights);
ChatRestrictionsInfo rights,
UserData *by,
TimeId since);
void setSaveCallback(
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> callback) {
@ -170,6 +174,8 @@ private:
TimeId getRealUntilValue() const;
const ChatRestrictionsInfo _oldRights;
UserData *_by = nullptr;
TimeId _since = 0;
TimeId _until = 0;
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> _saveCallback;

View file

@ -387,6 +387,24 @@ QString ParticipantsAdditionalData::adminRank(
return (i != end(_adminRanks)) ? i->second : QString();
}
TimeId ParticipantsAdditionalData::adminPromotedSince(
not_null<UserData*> user) const {
const auto i = _adminPromotedSince.find(user);
return (i != end(_adminPromotedSince)) ? i->second : TimeId(0);
}
TimeId ParticipantsAdditionalData::restrictedSince(
not_null<PeerData*> peer) const {
const auto i = _restrictedSince.find(peer);
return (i != end(_restrictedSince)) ? i->second : TimeId(0);
}
TimeId ParticipantsAdditionalData::memberSince(
not_null<UserData*> user) const {
const auto i = _memberSince.find(user);
return (i != end(_memberSince)) ? i->second : TimeId(0);
}
auto ParticipantsAdditionalData::restrictedRights(
not_null<PeerData*> participant) const
-> std::optional<ChatRestrictionsInfo> {
@ -689,6 +707,11 @@ UserData *ParticipantsAdditionalData::applyAdmin(
} else {
_adminRanks.remove(user);
}
if (data.promotedSince()) {
_adminPromotedSince[user] = data.promotedSince();
} else {
_adminPromotedSince.remove(user);
}
if (const auto by = _peer->owner().userLoaded(data.by())) {
const auto i = _adminPromotedBy.find(user);
if (i == _adminPromotedBy.end()) {
@ -741,6 +764,11 @@ PeerData *ParticipantsAdditionalData::applyBanned(
} else {
_kicked.erase(participant);
}
if (data.restrictedSince()) {
_restrictedSince[participant] = data.restrictedSince();
} else {
_restrictedSince.remove(participant);
}
_restrictedRights[participant] = data.restrictions();
if (const auto by = _peer->owner().userLoaded(data.by())) {
const auto i = _restrictedBy.find(participant);
@ -1720,7 +1748,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
_peer,
user,
currentRights,
_additional.adminRank(user));
_additional.adminRank(user),
_additional.adminPromotedSince(user),
_additional.adminPromotedBy(user));
if (_additional.canAddOrEditAdmin(user)) {
const auto done = crl::guard(this, [=](
ChatAdminRightsInfo newRights,
@ -1776,7 +1806,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
_peer,
user,
hasAdminRights,
currentRights);
currentRights,
_additional.restrictedBy(user),
_additional.restrictedSince(user));
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
ChatRestrictionsInfo newRights) {

View file

@ -106,14 +106,19 @@ public:
not_null<PeerData*> participant) const;
[[nodiscard]] std::optional<ChatAdminRightsInfo> adminRights(
not_null<UserData*> user) const;
QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] std::optional<ChatRestrictionsInfo> restrictedRights(
not_null<PeerData*> participant) const;
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *restrictedBy(
not_null<PeerData*> participant) const;
[[nodiscard]] TimeId adminPromotedSince(not_null<UserData*>) const;
[[nodiscard]] TimeId restrictedSince(not_null<PeerData*>) const;
[[nodiscard]] TimeId memberSince(not_null<UserData*>) const;
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
@ -144,6 +149,9 @@ private:
// Data for channels.
base::flat_map<not_null<UserData*>, ChatAdminRightsInfo> _adminRights;
base::flat_map<not_null<UserData*>, QString> _adminRanks;
base::flat_map<not_null<UserData*>, TimeId> _adminPromotedSince;
base::flat_map<not_null<PeerData*>, TimeId> _restrictedSince;
base::flat_map<not_null<UserData*>, TimeId> _memberSince;
base::flat_set<not_null<UserData*>> _adminCanEdit;
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
std::map<not_null<PeerData*>, ChatRestrictionsInfo> _restrictedRights;

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

@ -129,7 +129,9 @@ namespace {
not_null<Window::SessionController*> controller) {
using Limit = HistoryView::Controls::CharactersLimitLabel;
const auto wrap = box->verticalLayout()->add(
const auto bottomContainer = box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));
const auto wrap = bottomContainer->add(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding);
const auto input = Ui::CreateChild<Ui::InputField>(
@ -233,6 +235,7 @@ void SendGifWithCaptionBox(
}
box->setTitle(tr::lng_send_gif_with_caption());
box->setWidth(st::boxWidth);
box->getDelegate()->setStyle(st::sendGifBox);
const auto container = box->verticalLayout();
[[maybe_unused]] const auto gifWidget = AddGifWidget(

View file

@ -1505,3 +1505,7 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
font: font(15px semibold);
}
}
sendGifBox: Box(defaultBox) {
shadowIgnoreBottomSkip: true;
}

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 = 5005001;
constexpr auto AppVersionStr = "5.5.1";
constexpr auto AppVersion = 5005003;
constexpr auto AppVersionStr = "5.5.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -1509,15 +1509,28 @@ void InnerWidget::suggestRestrictParticipant(
}
_menu->addAction(tr::lng_context_restrict_user(tr::now), [=] {
const auto user = participant->asUser();
auto editRestrictions = [=](bool hasAdminRights, ChatRestrictionsInfo currentRights) {
auto editRestrictions = [=](
bool hasAdminRights,
ChatRestrictionsInfo currentRights,
UserData *by,
TimeId since) {
auto weak = QPointer<InnerWidget>(this);
auto weakBox = std::make_shared<QPointer<Ui::BoxContent>>();
auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, currentRights);
auto box = Box<EditRestrictedBox>(
_channel,
user,
hasAdminRights,
currentRights,
by,
since);
box->setSaveCallback([=](
ChatRestrictionsInfo oldRights,
ChatRestrictionsInfo newRights) {
if (weak) {
weak->restrictParticipant(participant, oldRights, newRights);
weak->restrictParticipant(
participant,
oldRights,
newRights);
}
if (*weakBox) {
(*weakBox)->closeBox();
@ -1544,29 +1557,30 @@ void InnerWidget::suggestRestrictParticipant(
});
*weakBox = _controller->show(Ui::MakeConfirmBox({ text, sure }));
} else if (base::contains(_admins, user)) {
editRestrictions(true, ChatRestrictionsInfo());
editRestrictions(true, {}, nullptr, 0);
} else {
_api.request(MTPchannels_GetParticipant(
_channel->inputChannel,
user->input
)).done([=](const MTPchannels_ChannelParticipant &result) {
Expects(result.type() == mtpc_channels_channelParticipant);
user->owner().processUsers(result.data().vusers());
auto &participant = result.c_channels_channelParticipant();
_channel->owner().processUsers(participant.vusers());
auto type = participant.vparticipant().type();
if (type == mtpc_channelParticipantBanned) {
auto &banned = participant.vparticipant().c_channelParticipantBanned();
const auto participant = Api::ChatParticipant(
result.data().vparticipant(),
user);
using Type = Api::ChatParticipant::Type;
if (participant.type() == Type::Creator
|| participant.type() == Type::Admin) {
editRestrictions(true, {}, nullptr, 0);
} else if (const auto since = participant.restrictedSince()) {
editRestrictions(
false,
ChatRestrictionsInfo(banned.vbanned_rights()));
} else {
auto hasAdminRights = (type == mtpc_channelParticipantAdmin)
|| (type == mtpc_channelParticipantCreator);
editRestrictions(hasAdminRights, ChatRestrictionsInfo());
participant.restrictions(),
user->owner().user(participant.by()),
since);
}
}).fail([=] {
editRestrictions(false, ChatRestrictionsInfo());
editRestrictions(false, {}, nullptr, 0);
}).send();
}
}, &st::menuIconPermissions);

View file

@ -2042,7 +2042,7 @@ void GenerateItems(
const auto participant = Api::ChatParticipant(
action.vnew_participant(),
channel);
if (participant.subscriptionDate().isNull()) {
if (!participant.subscriptionDate()) {
return;
}
const auto participantPeer = channel->owner().peer(participant.id());
@ -2050,13 +2050,15 @@ void GenerateItems(
const auto participantPeerLinkText = Ui::Text::Link(
participantPeer->name(),
QString());
const auto parsed = base::unixtime::parse(
participant.subscriptionDate());
addServiceMessageWithLink(
tr::lng_admin_log_subscription_extend(
tr::now,
lt_name,
participantPeerLinkText,
lt_date,
{ langDateTimeFull(participant.subscriptionDate()) },
{ langDateTimeFull(parsed) },
Ui::Text::WithEntities),
participantPeerLink);
};

View file

@ -4275,32 +4275,32 @@ auto HistoryInner::findViewForPinnedTracking(int top) const
return { nullptr, 0 };
}
void HistoryInner::refreshAboutView() {
void HistoryInner::refreshAboutView(bool force) {
const auto refresh = [&] {
if (force) {
_aboutView = nullptr;
}
if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
}
};
if (const auto user = _peer->asUser()) {
if (const auto info = user->botInfo.get()) {
if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
}
refresh();
if (!info->inited) {
session().api().requestFullPeer(user);
}
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()
&& !historyHeight()) {
if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
}
refresh();
} else if (!historyHeight()) {
if (!user->isFullLoaded()) {
session().api().requestFullPeer(user);
} else if (!_aboutView) {
_aboutView = std::make_unique<HistoryView::AboutView>(
_history,
_history->delegateMixin()->delegate());
} else {
refresh();
}
}
}

View file

@ -201,7 +201,7 @@ public:
[[nodiscard]] std::pair<Element*, int> findViewForPinnedTracking(
int top) const;
void refreshAboutView();
void refreshAboutView(bool force = false);
void notifyMigrateUpdated();
// Ui::AbstractTooltipShower interface.

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;
}
}
@ -8017,13 +8017,18 @@ void HistoryWidget::handlePeerUpdate() {
}
}
if (!_showAnimation) {
if (_unblock->isHidden() == isBlocked()
const auto blockChanged = (_unblock->isHidden() == isBlocked());
if (blockChanged
|| (!isBlocked() && _joinChannel->isHidden() == isJoinChannel())) {
resize = true;
}
if (updateCanSendMessage()) {
resize = true;
}
if (blockChanged) {
_list->refreshAboutView(true);
_list->updateBotInfo();
}
updateControlsVisibility();
if (resize) {
updateControlsGeometry();

View file

@ -245,6 +245,8 @@ bool AboutView::refresh() {
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
setItem(makePremiumRequired(), nullptr);
} else if (user->isBlocked()) {
setItem(makeBlocked(), nullptr);
} else {
makeIntro(user);
}
@ -393,4 +395,17 @@ AdminLog::OwnedItem AboutView::makePremiumRequired() {
return result;
}
AdminLog::OwnedItem AboutView::makeBlocked() {
const auto item = _history->makeMessage({
.id = _history->nextNonHistoryEntryId(),
.flags = (MessageFlag::FakeAboutView
| MessageFlag::FakeHistoryItem
| MessageFlag::Local),
.from = _history->peer->id,
}, PreparedServiceText{
{ tr::lng_chat_intro_default_title(tr::now) }
});
return AdminLog::OwnedItem(_delegate, item);
}
} // namespace HistoryView

View file

@ -36,6 +36,7 @@ public:
private:
[[nodiscard]] AdminLog::OwnedItem makeAboutBot(not_null<BotInfo*> info);
[[nodiscard]] AdminLog::OwnedItem makePremiumRequired();
[[nodiscard]] AdminLog::OwnedItem makeBlocked();
void makeIntro(not_null<UserData*> user);
void setItem(AdminLog::OwnedItem item, DocumentData *sticker);
void setHelloChosen(not_null<DocumentData*> sticker);

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

@ -6050,7 +6050,7 @@ void OverlayWidget::handleMouseRelease(
} else if (_over == Over::Video && _down == Over::Video) {
if (_stories) {
_stories->contentPressed(false);
} else if (_streamed) {
} else if (_streamed && !_window->mousePressCancelled()) {
playbackPauseResume();
}
} else if (_pressed) {

View file

@ -141,10 +141,12 @@ paymentsLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
}
botWebViewPanelSize: size(384px, 694px);
botWebViewBottomPadding: margins(12px, 12px, 12px, 12px);
botWebViewBottomSkip: point(12px, 8px);
botWebViewBottomButton: RoundButton(paymentsPanelSubmit) {
height: 56px;
height: 40px;
style: TextStyle(defaultTextStyle) {
font: boxButtonFont;
}
textTop: 19px;
textTop: 11px;
}

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

@ -560,9 +560,9 @@ filterLinkPreviewRadius: 13px;
filterLinkPreviewTop: 30px;
filterLinkPreviewColumn: 65px;
filterLinkPreviewAllBottom: 18px;
filterLinkPreviewAllTop: 17px;
filterLinkPreviewAllTop: 15px;
filterLinkPreviewMyBottom: 74px;
filterLinkPreviewMyTop: 73px;
filterLinkPreviewMyTop: 71px;
filterLinkPreviewChatSize: 36px;
filterLinkPreviewChatSkip: 10px;
filterLinkPreviewBadgeLeft: 40px;

View file

@ -133,16 +133,11 @@ private:
};
[[nodiscard]] bool UseSeparateWindow() {
return !Platform::IsWayland()
&& Ui::Platform::TranslucentWindowsSupported();
}
Preview::Preview(QWidget *slider, rpl::producer<QImage> userpic)
: _widget(slider->window())
, _slider(slider)
, _ratio(style::DevicePixelRatio())
, _window(UseSeparateWindow()) {
, _window(Ui::Platform::TranslucentWindowsSupported()) {
std::move(userpic) | rpl::start_with_next([=](QImage &&userpic) {
_userpicOriginal = std::move(userpic);
if (!_userpicImage.isNull()) {

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

@ -48,6 +48,26 @@ constexpr auto kProgressOpacity = 0.3;
constexpr auto kLightnessThreshold = 128;
constexpr auto kLightnessDelta = 32;
struct ButtonArgs {
bool isActive = false;
bool isVisible = false;
bool isProgressVisible = false;
QString text;
};
[[nodiscard]] RectPart ParsePosition(const QString &position) {
if (position == u"left"_q) {
return RectPart::Left;
} else if (position == u"top"_q) {
return RectPart::Top;
} else if (position == u"right"_q) {
return RectPart::Right;
} else if (position == u"bottom"_q) {
return RectPart::Bottom;
}
return RectPart::Left;
}
[[nodiscard]] QJsonObject ParseMethodArgs(const QString &json) {
if (json.isEmpty()) {
return {};
@ -99,6 +119,15 @@ constexpr auto kLightnessDelta = 32;
alpha);
}
[[nodiscard]] const style::color *LookupNamedColor(const QString &key) {
if (key == u"secondary_bg_color"_q) {
return &st::boxDividerBg;
} else if (key == u"bottom_bar_bg_color"_q) {
return &st::windowBg;
}
return nullptr;
}
} // namespace
class Panel::Button final : public RippleButton {
@ -107,8 +136,11 @@ public:
~Button();
void updateBg(QColor bg);
void updateBg(not_null<const style::color*> paletteBg);
void updateFg(QColor fg);
void updateArgs(MainButtonArgs &&args);
void updateFg(not_null<const style::color*> paletteFg);
void updateArgs(ButtonArgs &&args);
private:
void paintEvent(QPaintEvent *e) override;
@ -128,6 +160,9 @@ private:
style::owned_color _bg;
RoundRect _roundRect;
rpl::lifetime _bgLifetime;
rpl::lifetime _fgLifetime;
};
struct Panel::Progress {
@ -171,15 +206,33 @@ Panel::Button::~Button() = default;
void Panel::Button::updateBg(QColor bg) {
_bg.update(bg);
_roundRect.setColor(_bg.color());
_bgLifetime.destroy();
update();
}
void Panel::Button::updateBg(not_null<const style::color*> paletteBg) {
updateBg((*paletteBg)->c);
_bgLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
updateBg((*paletteBg)->c);
});
}
void Panel::Button::updateFg(QColor fg) {
_fg = fg;
_fgLifetime.destroy();
update();
}
void Panel::Button::updateArgs(MainButtonArgs &&args) {
void Panel::Button::updateFg(not_null<const style::color*> paletteFg) {
updateFg((*paletteFg)->c);
_fgLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
updateFg((*paletteFg)->c);
});
}
void Panel::Button::updateArgs(ButtonArgs &&args) {
_textFull = std::move(args.text);
setDisabled(!args.isActive);
setPointerCursor(false);
@ -266,10 +319,7 @@ void Panel::Button::setupProgressGeometry() {
void Panel::Button::paintEvent(QPaintEvent *e) {
Painter p(this);
_roundRect.paintSomeRounded(
p,
rect().marginsAdded({ 0, st::callRadius * 2, 0, 0 }),
RectPart::BottomLeft | RectPart::BottomRight);
_roundRect.paint(p, rect());
if (!isDisabled()) {
const auto ripple = ResolveRipple(_bg.color()->c);
@ -292,12 +342,7 @@ void Panel::Button::paintEvent(QPaintEvent *e) {
}
QImage Panel::Button::prepareRippleMask() const {
return RippleAnimation::MaskByDrawer(size(), false, [&](QPainter &p) {
p.drawRoundedRect(
rect().marginsAdded({ 0, st::callRadius * 2, 0, 0 }),
st::callRadius,
st::callRadius);
});
return RippleAnimation::RoundRectMask(size(), st::callRadius);
}
QPoint Panel::Button::prepareRippleStartPosition() const {
@ -599,7 +644,7 @@ void Panel::createWebviewBottom() {
) | rpl::start_with_next([=](QRect inner, int height) {
bottom->move(inner.x(), inner.y() + inner.height() - height);
bottom->resizeToWidth(inner.width());
updateFooterHeight();
layoutButtons();
}, bottom->lifetime());
}
@ -633,7 +678,9 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
}
if (_webviewBottom.get() == bottom) {
_webviewBottom = nullptr;
_secondaryButton = nullptr;
_mainButton = nullptr;
_bottomButtonsBg = nullptr;
}
});
if (!raw->widget()) {
@ -655,7 +702,6 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
});
});
updateFooterHeight();
rpl::combine(
container->geometryValue(),
_footerHeight.value()
@ -681,7 +727,9 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
} else if (command == "web_app_switch_inline_query") {
switchInlineQueryMessage(arguments);
} else if (command == "web_app_setup_main_button") {
processMainButtonMessage(arguments);
processButtonMessage(_mainButton, arguments);
} else if (command == "web_app_setup_secondary_button") {
processButtonMessage(_secondaryButton, arguments);
} else if (command == "web_app_setup_back_button") {
processBackButtonMessage(arguments);
} else if (command == "web_app_setup_settings_button") {
@ -714,6 +762,8 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
requestClipboardText(arguments);
} else if (command == "web_app_set_header_color") {
processHeaderColor(arguments);
} else if (command == "web_app_set_bottom_bar_color") {
processBottomBarColor(arguments);
} else if (command == "share_score") {
_delegate->botHandleMenuButton(MenuButton::ShareGame);
}
@ -745,6 +795,7 @@ postEvent: function(eventType, eventData) {
return false;
}
layoutButtons();
setupProgressGeometry();
base::qt_signal_producer(
@ -1096,12 +1147,12 @@ void Panel::requestClipboardText(const QJsonObject &args) {
}
bool Panel::allowOpenLink() const {
const auto now = crl::now();
if (_mainButtonLastClick
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
_mainButtonLastClick = 0;
return true;
}
//const auto now = crl::now();
//if (_mainButtonLastClick
// && _mainButtonLastClick + kProcessClickTimeout >= now) {
// _mainButtonLastClick = 0;
// return true;
//}
return true;
}
@ -1109,12 +1160,12 @@ bool Panel::allowClipboardQuery() const {
if (!_allowClipboardRead) {
return false;
}
const auto now = crl::now();
if (_mainButtonLastClick
&& _mainButtonLastClick + kProcessClickTimeout >= now) {
_mainButtonLastClick = 0;
return true;
}
//const auto now = crl::now();
//if (_mainButtonLastClick
// && _mainButtonLastClick + kProcessClickTimeout >= now) {
// _mainButtonLastClick = 0;
// return true;
//}
return true;
}
@ -1157,14 +1208,16 @@ void Panel::setupClosingBehaviour(const QJsonObject &args) {
_closeNeedConfirmation = args["need_confirmation"].toBool();
}
void Panel::processMainButtonMessage(const QJsonObject &args) {
void Panel::processButtonMessage(
std::unique_ptr<Button> &button,
const QJsonObject &args) {
if (args.isEmpty()) {
_delegate->botClose();
return;
}
const auto shown = [&] {
return _mainButton && !_mainButton->isHidden();
return button && !button->isHidden();
};
const auto wasShown = shown();
const auto guard = gsl::finally([&] {
@ -1175,42 +1228,38 @@ void Panel::processMainButtonMessage(const QJsonObject &args) {
}
});
if (!_mainButton) {
if (args["is_visible"].toBool()) {
createMainButton();
const auto text = args["text"].toString().trimmed();
const auto visible = args["is_visible"].toBool() && !text.isEmpty();
if (!button) {
if (visible) {
createButton(button);
_bottomButtonsBg->show();
} else {
return;
}
}
if (const auto bg = ParseColor(args["color"].toString())) {
_mainButton->updateBg(*bg);
_bgLifetime.destroy();
button->updateBg(*bg);
} else {
_mainButton->updateBg(st::windowBgActive->c);
_bgLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
_mainButton->updateBg(st::windowBgActive->c);
});
button->updateBg(&st::windowBgActive);
}
if (const auto fg = ParseColor(args["text_color"].toString())) {
_mainButton->updateFg(*fg);
_fgLifetime.destroy();
button->updateFg(*fg);
} else {
_mainButton->updateFg(st::windowFgActive->c);
_fgLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
_mainButton->updateFg(st::windowFgActive->c);
});
button->updateFg(&st::windowFgActive);
}
_mainButton->updateArgs({
button->updateArgs({
.isActive = args["is_active"].toBool(),
.isVisible = args["is_visible"].toBool(),
.isVisible = visible,
.isProgressVisible = args["is_progress_visible"].toBool(),
.text = args["text"].toString(),
});
if (button.get() == _secondaryButton.get()) {
_secondaryPosition = ParsePosition(args["position"].toString());
}
}
void Panel::processBackButtonMessage(const QJsonObject &args) {
@ -1225,11 +1274,12 @@ void Panel::processHeaderColor(const QJsonObject &args) {
if (const auto color = ParseColor(args["color"].toString())) {
_widget->overrideTitleColor(color);
_headerColorLifetime.destroy();
} else if (args["color_key"].toString() == u"secondary_bg_color"_q) {
_widget->overrideTitleColor(st::boxDividerBg->c);
} else if (const auto color = LookupNamedColor(
args["color_key"].toString())) {
_widget->overrideTitleColor((*color)->c);
_headerColorLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
_widget->overrideTitleColor(st::boxDividerBg->c);
_widget->overrideTitleColor((*color)->c);
});
} else {
_widget->overrideTitleColor(std::nullopt);
@ -1237,37 +1287,146 @@ void Panel::processHeaderColor(const QJsonObject &args) {
}
}
void Panel::createMainButton() {
_mainButton = std::make_unique<Button>(
_widget.get(),
st::botWebViewBottomButton);
const auto button = _mainButton.get();
button->setClickedCallback([=] {
if (!button->isDisabled()) {
postEvent("main_button_pressed");
_mainButtonLastClick = crl::now();
}
});
button->hide();
rpl::combine(
_webviewParent->geometryValue() | rpl::map([=] {
return _widget->innerGeometry();
}),
button->shownValue(),
button->heightValue()
) | rpl::start_with_next([=](QRect inner, bool shown, int height) {
button->move(inner.x(), inner.y() + inner.height() - height);
button->resizeToWidth(inner.width());
_webviewBottom->setVisible(!shown);
updateFooterHeight();
}, button->lifetime());
void Panel::processBottomBarColor(const QJsonObject &args) {
if (const auto color = ParseColor(args["color"].toString())) {
_widget->overrideBottomBarColor(color);
_bottomBarColor = color;
_bottomBarColorLifetime.destroy();
} else if (const auto color = LookupNamedColor(
args["color_key"].toString())) {
_widget->overrideBottomBarColor((*color)->c);
_bottomBarColor = (*color)->c;
_headerColorLifetime = style::PaletteChanged(
) | rpl::start_with_next([=] {
_widget->overrideBottomBarColor((*color)->c);
_bottomBarColor = (*color)->c;
});
} else {
_widget->overrideBottomBarColor(std::nullopt);
_bottomBarColor = std::nullopt;
_headerColorLifetime.destroy();
}
if (const auto raw = _bottomButtonsBg.get()) {
raw->update();
}
}
void Panel::updateFooterHeight() {
_footerHeight = (_mainButton && !_mainButton->isHidden())
? _mainButton->height()
void Panel::createButton(std::unique_ptr<Button> &button) {
if (!_bottomButtonsBg) {
_bottomButtonsBg = std::make_unique<RpWidget>(_widget.get());
const auto raw = _bottomButtonsBg.get();
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(_bottomBarColor.value_or(st::windowBg->c));
p.drawRoundedRect(
raw->rect().marginsAdded({ 0, 2 * st::callRadius, 0, 0 }),
st::callRadius,
st::callRadius);
}, raw->lifetime());
}
button = std::make_unique<Button>(
_bottomButtonsBg.get(),
st::botWebViewBottomButton);
const auto raw = button.get();
raw->setClickedCallback([=] {
if (!raw->isDisabled()) {
if (raw == _mainButton.get()) {
postEvent("main_button_pressed");
} else if (raw == _secondaryButton.get()) {
postEvent("secondary_button_pressed");
}
}
});
raw->hide();
rpl::combine(
raw->shownValue(),
raw->heightValue()
) | rpl::start_with_next([=] {
layoutButtons();
}, raw->lifetime());
}
void Panel::layoutButtons() {
const auto inner = _widget->innerGeometry();
const auto shown = [](std::unique_ptr<Button> &button) {
return button && !button->isHidden();
};
const auto any = shown(_mainButton) || shown(_secondaryButton);
_webviewBottom->setVisible(!any);
if (any) {
_bottomButtonsBg->show();
const auto one = shown(_mainButton)
? _mainButton.get()
: _secondaryButton.get();
const auto both = shown(_mainButton) && shown(_secondaryButton);
const auto vertical = both
&& ((_secondaryPosition == RectPart::Top)
|| (_secondaryPosition == RectPart::Bottom));
const auto padding = st::botWebViewBottomPadding;
const auto height = padding.top()
+ (vertical
? (_mainButton->height()
+ st::botWebViewBottomSkip.y()
+ _secondaryButton->height())
: one->height())
+ padding.bottom();
_bottomButtonsBg->setGeometry(
inner.x(),
inner.y() + inner.height() - height,
inner.width(),
height);
auto left = padding.left();
auto bottom = height - padding.bottom();
auto available = inner.width() - padding.left() - padding.right();
if (!both) {
one->resizeToWidth(available);
one->move(left, bottom - one->height());
} else if (_secondaryPosition == RectPart::Top) {
_mainButton->resizeToWidth(available);
bottom -= _mainButton->height();
_mainButton->move(left, bottom);
bottom -= st::botWebViewBottomSkip.y();
_secondaryButton->resizeToWidth(available);
bottom -= _secondaryButton->height();
_secondaryButton->move(left, bottom);
} else if (_secondaryPosition == RectPart::Bottom) {
_secondaryButton->resizeToWidth(available);
bottom -= _secondaryButton->height();
_secondaryButton->move(left, bottom);
bottom -= st::botWebViewBottomSkip.y();
_mainButton->resizeToWidth(available);
bottom -= _mainButton->height();
_mainButton->move(left, bottom);
} else if (_secondaryPosition == RectPart::Left) {
available = (available - st::botWebViewBottomSkip.x()) / 2;
_secondaryButton->resizeToWidth(available);
bottom -= _secondaryButton->height();
_secondaryButton->move(left, bottom);
_mainButton->resizeToWidth(available);
_mainButton->move(
inner.width() - padding.right() - available,
bottom);
} else {
available = (available - st::botWebViewBottomSkip.x()) / 2;
_mainButton->resizeToWidth(available);
bottom -= _mainButton->height();
_mainButton->move(left, bottom);
_secondaryButton->resizeToWidth(available);
_secondaryButton->move(
inner.width() - padding.right() - available,
bottom);
}
} else if (_bottomButtonsBg) {
_bottomButtonsBg->hide();
}
_footerHeight = any
? _bottomButtonsBg->height()
: _webviewBottom->height();
}

View file

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "base/weak_ptr.h"
#include "base/flags.h"
#include "ui/rect_part.h"
#include "ui/round_rect.h"
#include "webview/webview_common.h"
class QJsonObject;
@ -32,13 +34,6 @@ namespace Ui::BotWebView {
[[nodiscard]] TextWithEntities ErrorText(const Webview::Available &info);
struct MainButtonArgs {
bool isActive = false;
bool isVisible = false;
bool isProgressVisible = false;
QString text;
};
enum class MenuButton {
None = 0x00,
OpenBot = 0x01,
@ -126,10 +121,13 @@ private:
void setTitle(rpl::producer<QString> title);
void sendDataMessage(const QJsonObject &args);
void switchInlineQueryMessage(const QJsonObject &args);
void processMainButtonMessage(const QJsonObject &args);
void processButtonMessage(
std::unique_ptr<Button> &button,
const QJsonObject &args);
void processBackButtonMessage(const QJsonObject &args);
void processSettingsButtonMessage(const QJsonObject &args);
void processHeaderColor(const QJsonObject &args);
void processBottomBarColor(const QJsonObject &args);
void openTgLink(const QJsonObject &args);
void openExternalLink(const QJsonObject &args);
void openInvoice(const QJsonObject &args);
@ -144,7 +142,7 @@ private:
void replyCustomMethod(QJsonValue requestId, QJsonObject response);
void requestClipboardText(const QJsonObject &args);
void setupClosingBehaviour(const QJsonObject &args);
void createMainButton();
void createButton(std::unique_ptr<Button> &button);
void scheduleCloseWithConfirmation();
void closeWithConfirmation();
void sendViewport();
@ -158,7 +156,7 @@ private:
[[nodiscard]] bool progressWithBackground() const;
[[nodiscard]] QRect progressRect() const;
void setupProgressGeometry();
void updateFooterHeight();
void layoutButtons();
Webview::StorageId _storageId;
const not_null<Delegate*> _delegate;
@ -170,14 +168,16 @@ private:
std::unique_ptr<RpWidget> _webviewBottom;
rpl::variable<QString> _bottomText;
QPointer<RpWidget> _webviewParent;
std::unique_ptr<RpWidget> _bottomButtonsBg;
std::unique_ptr<Button> _mainButton;
mutable crl::time _mainButtonLastClick = 0;
std::unique_ptr<Button> _secondaryButton;
RectPart _secondaryPosition = RectPart::Left;
rpl::variable<int> _footerHeight = 0;
std::unique_ptr<Progress> _progress;
rpl::event_stream<> _themeUpdateForced;
std::optional<QColor> _bottomBarColor;
rpl::lifetime _headerColorLifetime;
rpl::lifetime _fgLifetime;
rpl::lifetime _bgLifetime;
rpl::lifetime _bottomBarColorLifetime;
bool _webviewProgress = false;
bool _themeUpdateScheduled = false;
bool _hiddenForPayment = false;

View file

@ -105,11 +105,22 @@ private:
icon->paint(p, iconLeft, myIconTop, size);
const auto paintName = [&](const QString &text, int top) {
const auto &font = st.style.font;
p.drawText(
QRect(0, top, column, font->height),
font->elided(text, available),
style::al_top);
auto string = Ui::Text::String(
st.style,
text,
kDefaultTextOptions,
available);
string.draw(p, {
.position = QPoint(
std::max(
(column - string.maxWidth()) / 2,
skip),
top),
.outerWidth = available,
.availableWidth = available,
.align = style::al_left,
.elisionLines = 1,
});
};
p.setFont(st.style.font);
p.setPen(st.textFg);
@ -456,4 +467,4 @@ object_ptr<RoundButton> FilterLinkProcessButton(
return result;
}
} // namespace Ui
} // namespace Ui

View file

@ -1503,6 +1503,7 @@ bool ReadPaletteValues(const QByteArray &content, Fn<bool(QLatin1String name, QL
{ "section_header_text_color", st::windowActiveTextFg },
{ "subtitle_text_color", st::windowSubTextFg },
{ "destructive_text_color", st::attentionButtonFg },
{ "bottom_bar_bg_color", st::windowBg },
};
auto object = QJsonObject();
const auto wrap = [](QColor color) {

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 5005001
AppVersion 5005003
AppVersionStrMajor 5.5
AppVersionStrSmall 5.5.1
AppVersionStr 5.5.1
AppVersionStrSmall 5.5.3
AppVersionStr 5.5.3
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 5.5.1
AppVersionOriginal 5.5.3

@ -1 +1 @@
Subproject commit 501f4c3502fd872ab4d777df8911bdac32de7c48
Subproject commit 6fdf60461444ba150e13ac36009c0ffce72c4c83

@ -1 +1 @@
Subproject commit 9ca74272a0ec33bac83c5b61a73d523902eb96cd
Subproject commit c4e3a08e6fb90a6174c8b592d9eb747dd4d3f9c5

@ -1 +1 @@
Subproject commit f3de8aa1a956459cea08b6c01ab746759cf5fa8b
Subproject commit 2de655f58dc327e40d5d9df71300a0d0fdb39c9f

View file

@ -1,3 +1,15 @@
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.
- Fix text layout outside of the message bubble glitch.
- Don't stop video when dragging media viewer in window mode.
5.5.1 (06.09.24)
- Fix crash in peer short info box.

2
cmake

@ -1 +1 @@
Subproject commit df8864ab76a0f0de8479d4a0de025e2b836cfb88
Subproject commit 05a7db2e2d2a59ecf42483debca4944d09154b5b