Compare commits
40 commits
49ee7ee52b
...
dab107cf90
Author | SHA1 | Date | |
---|---|---|---|
|
dab107cf90 | ||
|
d1d1aa3d21 | ||
|
d0536cc31f | ||
|
c47f5e9995 | ||
|
0916836ff9 | ||
|
5dd9ff1062 | ||
|
d1e3b9f15d | ||
|
ca3c179b75 | ||
|
c2d5924508 | ||
|
016d0395c3 | ||
|
925c9239bd | ||
|
fdcbe3cf3a | ||
|
f6b9cc5ce1 | ||
|
8c915e6dc3 | ||
|
1db426da2e | ||
|
d9be363962 | ||
|
88daa37e34 | ||
|
931fa01337 | ||
|
8e1595eb29 | ||
|
29e97232d8 | ||
|
f05191e668 | ||
|
1ba189e59d | ||
|
c40ca70aa6 | ||
|
521f991167 | ||
|
edf1417bbb | ||
|
686e9643ad | ||
|
975460d268 | ||
|
6b0c606d25 | ||
|
93ff0bdcff | ||
|
bf9d90ca4e | ||
|
d6e5e1e8f7 | ||
|
d3ae2ef9ea | ||
|
40b0854704 | ||
|
9aec6b6496 | ||
|
d01f977960 | ||
|
1444009ee2 | ||
|
14ee9bee26 | ||
|
93587ddc3c | ||
|
0c4d03477e | ||
|
a35092f012 |
68 changed files with 2450 additions and 1137 deletions
|
@ -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
|
||||
|
|
1
Telegram/Resources/icons/plane_white.svg
Normal file
1
Telegram/Resources/icons/plane_white.svg
Normal 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 |
BIN
Telegram/Resources/icons/qr_mini.png
Normal file
BIN
Telegram/Resources/icons/qr_mini.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 641 B |
BIN
Telegram/Resources/icons/qr_mini@2x.png
Normal file
BIN
Telegram/Resources/icons/qr_mini@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/qr_mini@3x.png
Normal file
BIN
Telegram/Resources/icons/qr_mini@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
|||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1505,3 +1505,7 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
|||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ".\\/");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -931,6 +931,7 @@ void Document::draw(
|
|||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||
.selection = selection,
|
||||
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||
.useFullWidth = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
875
Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp
Normal file
875
Telegram/SourceFiles/ui/boxes/peer_qr_box.cpp
Normal 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
|
22
Telegram/SourceFiles/ui/boxes/peer_qr_box.h
Normal file
22
Telegram/SourceFiles/ui/boxes/peer_qr_box.h
Normal 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
|
|
@ -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 ¶ms) {
|
|||
}
|
||||
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 ¶ms) {
|
|||
});
|
||||
});
|
||||
|
||||
updateFooterHeight();
|
||||
rpl::combine(
|
||||
container->geometryValue(),
|
||||
_footerHeight.value()
|
||||
|
@ -681,7 +727,9 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
|||
} 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 ¶ms) {
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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', """
|
||||
|
|
|
@ -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
|
|
@ -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
2
cmake
|
@ -1 +1 @@
|
|||
Subproject commit df8864ab76a0f0de8479d4a0de025e2b836cfb88
|
||||
Subproject commit 05a7db2e2d2a59ecf42483debca4944d09154b5b
|
Loading…
Reference in a new issue