Compare commits
152 Commits
| 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 | ||
|
|
49ee7ee52b | ||
|
|
52c77a1970 | ||
|
|
e4269ae7fb | ||
|
|
bd6011c524 | ||
|
|
46cb3ec103 | ||
|
|
0241129948 | ||
|
|
e44aca06cb | ||
|
|
19d386f977 | ||
|
|
a67fdda913 | ||
|
|
5286c7b1c3 | ||
|
|
d9d96d0a6f | ||
|
|
13353bb615 | ||
|
|
d78348fd16 | ||
|
|
1e8e660133 | ||
|
|
5196982c98 | ||
|
|
99f857fbf6 | ||
|
|
0982aa166a | ||
|
|
3ae9f86097 | ||
|
|
9efd9b0d68 | ||
|
|
267a73f355 | ||
|
|
437d8ea890 | ||
|
|
7ed92ec402 | ||
|
|
678d9ffbf9 | ||
|
|
bc4b427ed1 | ||
|
|
36141a9df9 | ||
|
|
d68ba75457 | ||
|
|
00ad32c5f4 | ||
|
|
064bab60ff | ||
|
|
5b146217c0 | ||
|
|
202c81b2e5 | ||
|
|
4520480604 | ||
|
|
aea87bb5cb | ||
|
|
19492f7e7b | ||
|
|
51030e3c45 | ||
|
|
e6b8b4be18 | ||
|
|
6ac13b7f80 | ||
|
|
7e5e6003a9 | ||
|
|
f445440995 | ||
|
|
d81547f091 | ||
|
|
2b185d491b | ||
|
|
ca47440950 | ||
|
|
5e32602f4a | ||
|
|
1f4516028c | ||
|
|
7f29d269a3 | ||
|
|
62c3374911 | ||
|
|
2116e04af5 | ||
|
|
a97d5e80c7 | ||
|
|
b5098038d0 | ||
|
|
6fce718252 | ||
|
|
40cf96202d | ||
|
|
c18e59218e | ||
|
|
216865a20d | ||
|
|
6c5036ee8d | ||
|
|
dc2f59ca24 | ||
|
|
6f6457137e | ||
|
|
bc5cb6e2a2 | ||
|
|
16825fff41 | ||
|
|
5024f1db8c | ||
|
|
a60385fc3d | ||
|
|
b20e2c37c1 | ||
|
|
acd40cbeb6 | ||
|
|
6a45a862dd | ||
|
|
5bd45e9a20 | ||
|
|
52e42f23ab | ||
|
|
96b7755cde | ||
|
|
c589ee1ca5 | ||
|
|
18c9ee093b | ||
|
|
b82fa3112c | ||
|
|
8d0f66d562 | ||
|
|
20a5e0ba73 | ||
|
|
5a2667c71e | ||
|
|
1f4a8d7eb6 | ||
|
|
4430bd0328 | ||
|
|
ab2e7f4c03 | ||
|
|
54e5c06b4d | ||
|
|
add5a6a0be | ||
|
|
89c2ba4293 | ||
|
|
dd100fb709 | ||
|
|
e8dd2b9e7b | ||
|
|
71e3cd227c | ||
|
|
1648c31a22 | ||
|
|
f8c820f319 | ||
|
|
300f35e78f | ||
|
|
2aa5849997 | ||
|
|
fb1b845211 | ||
|
|
3d2af9db8e | ||
|
|
6129e5a1cf | ||
|
|
7f70ee1227 | ||
|
|
c9f7da6e82 | ||
|
|
f956c0f227 | ||
|
|
df935e0477 | ||
|
|
841f1afe1e | ||
|
|
fb8b88557e | ||
|
|
2b502b22b9 | ||
|
|
5ac80d2655 | ||
|
|
8aa7499e63 | ||
|
|
8cd5e51982 | ||
|
|
e5c2133446 | ||
|
|
3dccdf2f05 | ||
|
|
8a708c6655 | ||
|
|
9e1d9eee4b | ||
|
|
f30aabc365 | ||
|
|
a5546d016f | ||
|
|
f79d70d112 | ||
|
|
ec28f258fb | ||
|
|
08f3a6fb40 | ||
|
|
8823d5256f | ||
|
|
60e7aa90d2 | ||
|
|
71357a9546 | ||
|
|
a5b06e9c56 | ||
|
|
0f94419f6d | ||
|
|
94e7aabea5 |
@@ -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.4.4.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,4,4,0
|
||||
PRODUCTVERSION 5,4,4,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.4.4.0"
|
||||
VALUE "FileVersion", "5.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.4.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,4,4,0
|
||||
PRODUCTVERSION 5,4,4,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.4.4.0"
|
||||
VALUE "FileVersion", "5.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.4.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +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 = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
if (data.vsubscription_until_date()) {
|
||||
_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
|
||||
@@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
|
||||
return _rights;
|
||||
}
|
||||
|
||||
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,6 +60,11 @@ public:
|
||||
ChatRestrictionsInfo restrictions() const;
|
||||
ChatAdminRightsInfo rights() const;
|
||||
|
||||
TimeId subscriptionDate() const;
|
||||
TimeId promotedSince() const;
|
||||
TimeId restrictedSince() const;
|
||||
TimeId memberSince() const;
|
||||
|
||||
Type type() const;
|
||||
QString rank() const;
|
||||
|
||||
@@ -73,6 +78,8 @@ private:
|
||||
bool _canBeEdited = false;
|
||||
|
||||
QString _rank;
|
||||
TimeId _subscriptionDate = 0;
|
||||
TimeId _date = 0;
|
||||
|
||||
ChatRestrictionsInfo _restrictions;
|
||||
ChatAdminRightsInfo _rights;
|
||||
|
||||
@@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -215,6 +217,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
@@ -294,10 +300,6 @@ void CreditsHistory::requestSubscriptions(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
@@ -385,4 +387,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsGiveawayOption;
|
||||
|
||||
const auto optionsFromTL = [=](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditsGiveawayOption{
|
||||
.winners = ranges::views::all(
|
||||
option.data().vwinners().v
|
||||
) | ranges::views::transform([](const auto &winner) {
|
||||
return Data::CreditsGiveawayOption::Winner{
|
||||
.users = winner.data().vusers().v,
|
||||
.perUserStars = winner.data().vper_user_stars().v,
|
||||
.isDefault = winner.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector,
|
||||
.storeProduct = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.credits = option.data().vstars().v,
|
||||
.yearlyBoosts = option.data().vyearly_boosts().v,
|
||||
.isExtended = option.data().is_extended(),
|
||||
.isDefault = option.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
};
|
||||
|
||||
_api.request(MTPpayments_GetStarsGiveawayOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,6 +36,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class CreditsGiveawayOptions final {
|
||||
public:
|
||||
CreditsGiveawayOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditsGiveawayOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
@@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
update(
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
@@ -67,7 +72,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.activatedCount = data.vactivated_count().value_or_empty();
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
info.credits = data.vstars_prize().value_or_empty();
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
@@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
invoice.creditsAmount
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
@@ -537,10 +540,10 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
|
||||
@@ -57,6 +57,7 @@ struct GiveawayInfo {
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
uint64 credits = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
|
||||
@@ -44,7 +44,10 @@ void InnerFillMessagePostFlags(
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!peer->amAnonymous()) {
|
||||
if (!peer->amAnonymous()
|
||||
|| (!peer->isBroadcast()
|
||||
&& options.sendAs
|
||||
&& options.sendAs != peer)) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
}
|
||||
const auto channel = peer->asBroadcast();
|
||||
|
||||
@@ -571,13 +571,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
return r.match([&](const MTPDprepaidGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.months = data.vmonths().v,
|
||||
.quantity = data.vquantity().v,
|
||||
};
|
||||
}, [&](const MTPDprepaidStarsGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.credits = data.vstars().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.boosts = data.vboosts().v,
|
||||
};
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
@@ -635,19 +644,21 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
.id = qs(data.vid()),
|
||||
.userId = UserId(data.vuser_id().value_or_empty()),
|
||||
.giveawayMessage = data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.expiresAt = base::unixtime::parse(data.vexpires().v),
|
||||
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
|
||||
/ kMonthsDivider),
|
||||
.giftCodeLink = std::move(giftCodeLink),
|
||||
.multiplier = data.vmultiplier().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.isGift = data.is_gift(),
|
||||
.isGiveaway = data.is_giveaway(),
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_ringtones.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
@@ -2622,6 +2623,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,10 @@ struct State {
|
||||
|
||||
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
|
||||
size *= style::DevicePixelRatio();
|
||||
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
|
||||
auto result = PeerData::GenerateUserpicImage(
|
||||
userpic.peer,
|
||||
userpic.view,
|
||||
size);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4160,8 +4160,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
MTP_vector<MTPInputMedia>(1, media),
|
||||
MTPstring()))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
@@ -4232,8 +4234,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias)),
|
||||
MTPstring()),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
|
||||
@@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
||||
@@ -597,8 +597,6 @@ rightsHeaderLabel: FlatLabel(boxLabel) {
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
rightsUntilMargin: margins(0px, 8px, 0px, 20px);
|
||||
rightsRankMargin: margins(0px, 7px, 0px, 20px);
|
||||
|
||||
groupStickersRemove: defaultMultiSelectSearchCancel;
|
||||
groupStickersRemovePosition: point(6px, 6px);
|
||||
@@ -787,7 +785,7 @@ backgroundConfirmPadding: margins(24px, 16px, 24px, 16px);
|
||||
backgroundConfirm: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
textFg: mediaviewSaveMsgFg;
|
||||
@@ -799,7 +797,7 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: shadowFg;
|
||||
@@ -951,7 +949,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
@@ -1122,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);
|
||||
|
||||
@@ -195,7 +195,7 @@ PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, { x, y, size, size });
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -581,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()));
|
||||
@@ -889,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()));
|
||||
@@ -965,9 +968,9 @@ void LinksController::rowPaintIcon(
|
||||
p.setBrush(*bg);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(QRect(0, 0, inner, inner));
|
||||
p.drawEllipse(Rect(Size(inner)));
|
||||
}
|
||||
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
|
||||
st::inviteLinkIcon.paintInCenter(p, Rect(Size(inner)));
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
}
|
||||
@@ -1113,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ void GiftCreditsBox(
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
u"internal:stars_examples"_q);
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
|
||||
@@ -69,6 +69,24 @@ constexpr auto kUserpicsMax = size_t(3);
|
||||
using GiftOption = Data::PremiumSubscriptionOption;
|
||||
using GiftOptions = Data::PremiumSubscriptionOptions;
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
uint64 messageId) {
|
||||
if (const auto msgId = MsgId(peerId ? messageId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
return session->createInternalLink(query);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
const auto gifts = data.vpremium_gifts();
|
||||
@@ -1419,21 +1437,56 @@ void GiveawayInfoBox(
|
||||
: !start->channels.empty()
|
||||
? start->channels.front()->name()
|
||||
: u"channel"_q;
|
||||
auto text = TextWithEntities();
|
||||
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
auto resultText = (!info.giftCode.isEmpty())
|
||||
? tr::lng_prizes_you_won(
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
|
||||
text.append("\n\n");
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
|
||||
text.append("\n\n");
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.credits)
|
||||
? tr::lng_prizes_you_won_credits(
|
||||
lt_amount,
|
||||
tr::lng_prizes_you_won_credits_amount(
|
||||
lt_count,
|
||||
rpl::single(float64(info.credits)),
|
||||
Ui::Text::Bold),
|
||||
lt_cup,
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.state == State::Finished)
|
||||
? tr::lng_prizes_you_didnt(Ui::Text::WithEntities)
|
||||
: (rpl::producer<TextWithEntities>)(nullptr);
|
||||
|
||||
if (resultText) {
|
||||
const auto &st = st::changePhoneDescription;
|
||||
const auto skip = st.style.font->height * 0.5;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
std::move(resultText),
|
||||
st);
|
||||
if ((!info.giftCode.isEmpty()) || info.credits) {
|
||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||
}
|
||||
const auto result = box->addRow(
|
||||
object_ptr<Ui::PaddingWrap<Ui::CenterWrap<Ui::FlatLabel>>>(
|
||||
box.get(),
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box.get(),
|
||||
std::move(label)),
|
||||
QMargins(0, skip, 0, skip)));
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxDividerBg);
|
||||
p.drawRoundedRect(result->rect(), st::boxRadius, st::boxRadius);
|
||||
}, result->lifetime());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
|
||||
auto text = TextWithEntities();
|
||||
|
||||
const auto quantity = start
|
||||
? start->quantity
|
||||
: (results->winnersCount + results->unclaimedCount);
|
||||
@@ -1442,22 +1495,39 @@ void GiveawayInfoBox(
|
||||
? results->channel->isMegagroup()
|
||||
: (!start->channels.empty()
|
||||
&& start->channels.front()->isMegagroup());
|
||||
const auto credits = start
|
||||
? start->credits
|
||||
: (results ? results->credits : 0);
|
||||
text.append((finished
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
lt_admins,
|
||||
(group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
credits
|
||||
? (group
|
||||
? tr::lng_prizes_credits_admins_group
|
||||
: tr::lng_prizes_credits_admins)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_amount,
|
||||
tr::lng_prizes_credits_admins_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
float64(credits),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::RichLangValue)
|
||||
: (group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
? (start->channels.size() > 1)
|
||||
@@ -1651,6 +1721,7 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
@@ -1658,15 +1729,12 @@ void AddCreditsHistoryEntryTable(
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto session = &controller->session();
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
const auto link = session->createInternalLink(query);
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareMsgId);
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(Ui::Text::Link(link)),
|
||||
@@ -1717,6 +1785,37 @@ void AddCreditsHistoryEntryTable(
|
||||
tr::lng_credits_box_history_entry_via_premium_bot(
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (entry.bareGiveawayMsgId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
controller->session().userId());
|
||||
}
|
||||
if (entry.bareGiveawayMsgId && entry.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(entry.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareGiveawayMsgId);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
@@ -1813,3 +1912,60 @@ void AddSubscriberEntryTable(
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
|
||||
}
|
||||
}
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &b) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = b.giveawayMessage.peer;
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
const auto from = controller->session().data().peer(peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
from->id);
|
||||
if (b.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(b.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
&controller->session(),
|
||||
peerId,
|
||||
b.giveawayMessage.msg.bare);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!b.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.date))));
|
||||
}
|
||||
if (!b.expiresAt.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_until(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.expiresAt))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
@@ -89,3 +90,8 @@ void AddSubscriberEntryTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -436,14 +436,16 @@ void CreateModerateMessagesBox(
|
||||
return result;
|
||||
}();
|
||||
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
rpl::conditional(
|
||||
rpl::single(isSingle),
|
||||
tr::lng_restrict_users_part_single_header(),
|
||||
tr::lng_restrict_users_part_header(
|
||||
lt_count,
|
||||
rpl::single(participants.size()) | tr::to_count())),
|
||||
rpl::single(participants.size()) | tr::to_count())));
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -63,6 +64,10 @@ public:
|
||||
template <typename Widget>
|
||||
Widget *addControl(object_ptr<Widget> widget, QMargins margin);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
|
||||
return _rows;
|
||||
}
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -164,6 +169,10 @@ EditParticipantBox::EditParticipantBox(
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
}
|
||||
|
||||
not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
|
||||
return _inner->verticalLayout();
|
||||
}
|
||||
|
||||
void EditParticipantBox::prepare() {
|
||||
_inner = setInnerWidget(object_ptr<Inner>(
|
||||
this,
|
||||
@@ -197,6 +206,8 @@ EditAdminBox::EditAdminBox(
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot)
|
||||
: EditParticipantBox(
|
||||
nullptr,
|
||||
@@ -205,6 +216,8 @@ EditAdminBox::EditAdminBox(
|
||||
(rights.flags != 0))
|
||||
, _oldRights(rights)
|
||||
, _oldRank(rank)
|
||||
, _promotedSince(promotedSince)
|
||||
, _by(by)
|
||||
, _addingBot(std::move(addingBot)) {
|
||||
}
|
||||
|
||||
@@ -279,9 +292,26 @@ void EditAdminBox::prepare() {
|
||||
object_ptr<Ui::VerticalLayout>(this)));
|
||||
const auto inner = _adminControlsWrap->entity();
|
||||
|
||||
inner->add(
|
||||
object_ptr<Ui::BoxContentDivider>(inner),
|
||||
st::rightsDividerMargin);
|
||||
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();
|
||||
@@ -335,9 +365,9 @@ void EditAdminBox::prepare() {
|
||||
.isForum = peer()->isForum(),
|
||||
.anyoneCanAddMembers = anyoneCanAddMembers,
|
||||
};
|
||||
Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
|
||||
auto [checkboxes, getChecked, changes] = CreateEditAdminRights(
|
||||
inner,
|
||||
tr::lng_rights_edit_admin_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
options);
|
||||
@@ -348,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);
|
||||
@@ -373,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())
|
||||
@@ -441,9 +499,7 @@ void EditAdminBox::refreshButtons() {
|
||||
|
||||
not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
st::rightsRankMargin);
|
||||
// Ui::AddDivider(container);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -480,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;
|
||||
}
|
||||
@@ -681,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() {
|
||||
@@ -712,9 +758,8 @@ void EditRestrictedBox::prepare() {
|
||||
|
||||
setTitle(tr::lng_rights_user_restrictions());
|
||||
|
||||
addControl(
|
||||
object_ptr<Ui::BoxContentDivider>(this),
|
||||
st::rightsDividerMargin);
|
||||
Ui::AddDivider(verticalLayout());
|
||||
Ui::AddSkip(verticalLayout());
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -749,16 +794,20 @@ void EditRestrictedBox::prepare() {
|
||||
return result;
|
||||
}();
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
verticalLayout(),
|
||||
tr::lng_rights_user_restrictions_header());
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
this,
|
||||
tr::lng_rights_user_restrictions_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer()->isForum() });
|
||||
addControl(std::move(checkboxes), QMargins());
|
||||
|
||||
_until = prepareRights.until;
|
||||
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
|
||||
addControl(
|
||||
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
|
||||
Ui::AddDivider(verticalLayout());
|
||||
addControl(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
@@ -773,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) {
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
@@ -77,6 +79,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot = {});
|
||||
|
||||
void setSaveCallback(
|
||||
@@ -108,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,
|
||||
@@ -125,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;
|
||||
|
||||
};
|
||||
@@ -144,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) {
|
||||
@@ -168,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()));
|
||||
@@ -734,7 +735,7 @@ void LinksController::rowPaintIcon(
|
||||
} else {
|
||||
(color == Color::Revoked
|
||||
? st::inviteLinkRevokedIcon
|
||||
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
|
||||
: st::inviteLinkIcon).paintInCenter(p, Rect(Size(inner)));
|
||||
}
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -583,14 +584,6 @@ template <typename Flags>
|
||||
ApplyDependencies(state->checkViews, dependencies, view);
|
||||
};
|
||||
|
||||
if (descriptor.header) {
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(descriptor.header),
|
||||
st::rightsHeaderLabel),
|
||||
st::rightsHeaderMargin);
|
||||
}
|
||||
const auto addCheckbox = [&](
|
||||
not_null<Ui::VerticalLayout*> verticalLayout,
|
||||
bool isInner,
|
||||
@@ -1146,9 +1139,11 @@ void ShowEditPeerPermissionsBox(
|
||||
return result;
|
||||
}();
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
tr::lng_rights_default_restrictions_header());
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
inner,
|
||||
tr::lng_rights_default_restrictions_header(),
|
||||
restrictions,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
@@ -1312,7 +1307,6 @@ std::vector<AdminRightLabel> AdminRightLabels(
|
||||
|
||||
EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatRestrictions restrictions,
|
||||
base::flat_map<ChatRestrictions, QString> disabledMessages,
|
||||
Data::RestrictionsSetOptions options) {
|
||||
@@ -1321,7 +1315,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
widget.data(),
|
||||
NegateRestrictions(restrictions),
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedRestrictionLabelsList(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
@@ -1338,7 +1331,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
|
||||
EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatAdminRights rights,
|
||||
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
||||
Data::AdminRightsSetOptions options) {
|
||||
@@ -1347,7 +1339,6 @@ EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
widget.data(),
|
||||
rights,
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedAdminRightLabels(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
|
||||
@@ -73,7 +73,6 @@ struct NestedEditFlagsLabels {
|
||||
|
||||
template <typename Flags>
|
||||
struct EditFlagsDescriptor {
|
||||
rpl::producer<QString> header;
|
||||
std::vector<NestedEditFlagsLabels<Flags>> labels;
|
||||
base::flat_map<Flags, QString> disabledMessages;
|
||||
const style::SettingsButton *st = nullptr;
|
||||
@@ -90,7 +89,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
|
||||
|
||||
[[nodiscard]] auto CreateEditRestrictions(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatRestrictions restrictions,
|
||||
base::flat_map<ChatRestrictions, QString> disabledMessages,
|
||||
Data::RestrictionsSetOptions options)
|
||||
@@ -98,7 +96,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
|
||||
|
||||
[[nodiscard]] auto CreateEditAdminRights(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatAdminRights rights,
|
||||
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
||||
Data::AdminRightsSetOptions options)
|
||||
|
||||
@@ -241,8 +241,8 @@ RequestsBoxController::RowHelper::RowHelper(bool isGroup)
|
||||
? tr::lng_group_requests_add(tr::now)
|
||||
: tr::lng_group_requests_add_channel(tr::now))
|
||||
, _rejectText(tr::lng_group_requests_dismiss(tr::now))
|
||||
, _acceptTextWidth(st::requestsAcceptButton.font->width(_acceptText))
|
||||
, _rejectTextWidth(st::requestsRejectButton.font->width(_rejectText)) {
|
||||
, _acceptTextWidth(st::requestsAcceptButton.style.font->width(_acceptText))
|
||||
, _rejectTextWidth(st::requestsRejectButton.style.font->width(_rejectText)) {
|
||||
}
|
||||
|
||||
RequestsBoxController::RequestsBoxController(
|
||||
@@ -491,7 +491,7 @@ void RequestsBoxController::RowHelper::paintButton(
|
||||
const auto textLeft = geometry.x()
|
||||
+ ((geometry.width() - textWidth) / 2);
|
||||
const auto textTop = geometry.y() + st.textTop;
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(over ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(textLeft, textTop, outerWidth, text);
|
||||
}
|
||||
|
||||
@@ -721,6 +721,7 @@ void PeerShortInfoBox::prepare() {
|
||||
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
refreshRoundedTopImage(getDelegate()->style().bg->c);
|
||||
|
||||
setCustomCornersFilling(RectPart::FullTop);
|
||||
setDimensionsToContent(st::shortInfoWidth, _rows);
|
||||
}
|
||||
|
||||
@@ -795,10 +796,6 @@ void PeerShortInfoBox::prepareRows() {
|
||||
tr::lng_mediaview_copy(tr::now));
|
||||
}
|
||||
|
||||
RectParts PeerShortInfoBox::customCornersFilling() {
|
||||
return RectPart::FullTop;
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ public:
|
||||
private:
|
||||
void prepare() override;
|
||||
void prepareRows();
|
||||
RectParts customCornersFilling() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ void ProcessUserpic(
|
||||
if (!state->userpicView.cloud) {
|
||||
GenerateImage(
|
||||
state,
|
||||
peer->generateUserpicImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
peer,
|
||||
state->userpicView,
|
||||
st::shortInfoWidth * style::DevicePixelRatio(),
|
||||
0),
|
||||
|
||||
@@ -23,7 +23,7 @@ using Type = SelfDestructionBox::Type;
|
||||
|
||||
[[nodiscard]] std::vector<int> Values(Type type) {
|
||||
switch (type) {
|
||||
case Type::Account: return { 30, 90, 180, 365 };
|
||||
case Type::Account: return { 30, 90, 180, 365, 548, 720 };
|
||||
case Type::Sessions: return { 7, 30, 90, 180, 365 };
|
||||
}
|
||||
Unexpected("SelfDestructionBox::Type in Values.");
|
||||
@@ -113,8 +113,8 @@ void SelfDestructionBox::showContent() {
|
||||
QString SelfDestructionBox::DaysLabel(int days) {
|
||||
return !days
|
||||
? QString()
|
||||
: (days > 364)
|
||||
? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
//: (days > 364)
|
||||
//? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
: (days > 25)
|
||||
? tr::lng_months(tr::now, lt_count, std::max(days / 30, 1))
|
||||
: tr::lng_weeks(tr::now, lt_count, std::max(days / 7, 1));
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1216,11 +1216,12 @@ StickersBox::Inner::Inner(
|
||||
})
|
||||
, _itemsTop(st::lineWidth)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now))
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
|
||||
, _undoText(tr::lng_stickers_return(tr::now))
|
||||
, _undoWidth(st::stickersUndoRemove.font->width(_undoText))
|
||||
, _undoWidth(st::stickersUndoRemove.style.font->width(_undoText))
|
||||
, _installedText(tr::lng_stickers_featured_installed(tr::now))
|
||||
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText)) {
|
||||
, _installedWidth(st::stickersTrendingInstalled.style.font->width(
|
||||
_installedText)) {
|
||||
setup();
|
||||
}
|
||||
|
||||
@@ -1666,7 +1667,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(st.textFg);
|
||||
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
|
||||
} else {
|
||||
@@ -1700,7 +1701,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(selected ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
|
||||
}
|
||||
|
||||
@@ -460,7 +460,8 @@ void Viewport::RendererGL::validateUserpicFrame(
|
||||
return;
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
tileData.userpicFrame = tile->row()->peer()->generateUserpicImage(
|
||||
tileData.userpicFrame = PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0);
|
||||
|
||||
@@ -77,7 +77,8 @@ void Viewport::RendererSW::validateUserpicFrame(
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
data.userpicFrame = Images::BlurLargeImage(
|
||||
tile->row()->peer()->generateUserpicImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0),
|
||||
|
||||
@@ -296,7 +296,9 @@ emojiPanButton: RoundButton(defaultActiveButton) {
|
||||
textTop: 2px;
|
||||
}
|
||||
emojiPanExpand: RoundButton(defaultActiveButton) {
|
||||
font: font(12px bold);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(12px bold);
|
||||
}
|
||||
width: -8px;
|
||||
height: 19px;
|
||||
textTop: 1px;
|
||||
@@ -1499,5 +1501,11 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 11px;
|
||||
width: -96px;
|
||||
font: font(15px semibold);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
||||
@@ -1450,17 +1450,17 @@ void EmojiListWidget::drawCollapsedBadge(
|
||||
int count) {
|
||||
const auto &st = st::emojiPanExpand;
|
||||
const auto text = u"+%1"_q.arg(count - _columnCount * kCollapsedRows + 1);
|
||||
const auto textWidth = st.font->width(text);
|
||||
const auto textWidth = st.style.font->width(text);
|
||||
const auto buttonw = std::max(textWidth - st.width, st.height);
|
||||
const auto buttonh = st.height;
|
||||
const auto buttonx = position.x() + (_singleSize.width() - buttonw) / 2;
|
||||
const auto buttony = position.y() + (_singleSize.height() - buttonh) / 2;
|
||||
_collapsedBg.paint(p, QRect(buttonx, buttony, buttonw, buttonh));
|
||||
p.setPen(this->st().bg);
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.drawText(
|
||||
buttonx + (buttonw - textWidth) / 2,
|
||||
(buttony + st.textTop + st.font->ascent),
|
||||
(buttony + st.textTop + st.style.font->ascent),
|
||||
text);
|
||||
}
|
||||
|
||||
@@ -2546,12 +2546,12 @@ int EmojiListWidget::paintButtonGetWidth(
|
||||
: selected
|
||||
? st::emojiPanButton.textFgOver
|
||||
: st::emojiPanButton.textFg);
|
||||
p.setFont(st::emojiPanButton.font);
|
||||
p.setFont(st::emojiPanButton.style.font);
|
||||
p.drawText(
|
||||
rect.x() - (st::emojiPanButton.width / 2),
|
||||
(rect.y()
|
||||
+ st::emojiPanButton.textTop
|
||||
+ st::emojiPanButton.font->ascent),
|
||||
+ st::emojiPanButton.style.font->ascent),
|
||||
button.text);
|
||||
return emojiRight() - rect.x();
|
||||
}
|
||||
@@ -2678,7 +2678,7 @@ void EmojiListWidget::initButton(
|
||||
const QString &text,
|
||||
bool gradient) {
|
||||
button.text = text;
|
||||
button.textWidth = st::emojiPanButton.font->width(text);
|
||||
button.textWidth = st::emojiPanButton.style.font->width(text);
|
||||
const auto width = button.textWidth - st::emojiPanButton.width;
|
||||
const auto height = st::emojiPanButton.height;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
|
||||
@@ -213,11 +213,14 @@ StickersListWidget::StickersListWidget(
|
||||
st().pathBg,
|
||||
st().pathFg,
|
||||
[=] { update(); }))
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft)
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird
|
||||
- st::emojiScroll.width
|
||||
- st().headerLeft)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now))
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
|
||||
, _installedText(tr::lng_stickers_featured_installed(tr::now))
|
||||
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
|
||||
, _installedWidth(
|
||||
st::stickersTrendingInstalled.style.font->width(_installedText))
|
||||
, _settings(this, tr::lng_stickers_you_have(tr::now))
|
||||
, _previewTimer([=] { showPreview(); })
|
||||
, _premiumMark(std::make_unique<StickerPremiumMark>(
|
||||
@@ -974,7 +977,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
const auto &st = installedSet
|
||||
? st::stickersTrendingInstalled
|
||||
: st::stickersTrendingAdd;
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(selected ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(
|
||||
add.x() - (st.width / 2),
|
||||
@@ -1238,7 +1241,7 @@ void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSe
|
||||
_megagroupSetButtonRipple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st::stickerGroupCategoryAdd.font);
|
||||
p.setFont(st::stickerGroupCategoryAdd.style.font);
|
||||
p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg);
|
||||
p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth);
|
||||
}
|
||||
@@ -2734,7 +2737,7 @@ void StickersListWidget::refreshMegagroupSetGeometry() {
|
||||
auto left = megagroupSetInfoLeft();
|
||||
auto availableWidth = (width() - left);
|
||||
auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top();
|
||||
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.font->width(_megagroupSetButtonText);
|
||||
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.style.font->width(_megagroupSetButtonText);
|
||||
auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width;
|
||||
_megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/update_checker.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "dialogs/ui/dialogs_suggestions.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_birthday_box.h"
|
||||
@@ -923,6 +924,17 @@ bool ShowCollectibleUsername(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Dialogs::StarsExamplesBox(controller));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -1380,6 +1392,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
ShowCollectibleUsername,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -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 = 5004004;
|
||||
constexpr auto AppVersionStr = "5.4.4";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 5005003;
|
||||
constexpr auto AppVersionStr = "5.5.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -27,10 +27,6 @@ struct GiftCodeLink final {
|
||||
};
|
||||
|
||||
struct Boost final {
|
||||
bool isGift = false;
|
||||
bool isGiveaway = false;
|
||||
bool isUnclaimed = false;
|
||||
|
||||
QString id;
|
||||
UserId userId = UserId(0);
|
||||
FullMsgId giveawayMessage;
|
||||
@@ -39,6 +35,11 @@ struct Boost final {
|
||||
int expiresAfterMonths = 0;
|
||||
GiftCodeLink giftCodeLink;
|
||||
int multiplier = 0;
|
||||
uint64 credits = 0;
|
||||
|
||||
bool isGift = false;
|
||||
bool isGiveaway = false;
|
||||
bool isUnclaimed = false;
|
||||
};
|
||||
|
||||
struct BoostsListSlice final {
|
||||
@@ -53,10 +54,12 @@ struct BoostsListSlice final {
|
||||
};
|
||||
|
||||
struct BoostPrepaidGiveaway final {
|
||||
int months = 0;
|
||||
uint64 id = 0;
|
||||
int quantity = 0;
|
||||
QDateTime date;
|
||||
uint64 id = 0;
|
||||
uint64 credits = 0;
|
||||
int months = 0;
|
||||
int quantity = 0;
|
||||
int boosts = 0;
|
||||
};
|
||||
|
||||
struct BoostStatus final {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_channel.h"
|
||||
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -971,6 +972,9 @@ PeerId ChannelData::groupCallDefaultJoinAs() const {
|
||||
|
||||
void ChannelData::setAllowedReactions(Data::AllowedReactions value) {
|
||||
if (_allowedReactions != value) {
|
||||
if (value.paidEnabled) {
|
||||
session().api().globalPrivacy().loadPaidReactionAnonymous();
|
||||
}
|
||||
const auto enabled = [](const Data::AllowedReactions &allowed) {
|
||||
return (allowed.type != Data::AllowedReactionsType::Some)
|
||||
|| !allowed.some.empty()
|
||||
|
||||
@@ -57,6 +57,7 @@ struct CreditsHistoryEntry final {
|
||||
uint64 credits = 0;
|
||||
uint64 bareMsgId = 0;
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
@@ -80,4 +81,22 @@ struct CreditsStatusSlice final {
|
||||
OffsetToken tokenSubscriptions;
|
||||
};
|
||||
|
||||
struct CreditsGiveawayOption final {
|
||||
struct Winner final {
|
||||
int users = 0;
|
||||
uint64 perUserStars = 0;
|
||||
bool isDefault = false;
|
||||
};
|
||||
std::vector<Winner> winners;
|
||||
QString storeProduct;
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
uint64 credits = 0;
|
||||
int yearlyBoosts = 0;
|
||||
bool isExtended = false;
|
||||
bool isDefault = false;
|
||||
};
|
||||
|
||||
using CreditsGiveawayOptions = std::vector<CreditsGiveawayOption>;
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -70,7 +70,7 @@ constexpr auto kShowChatNamesCount = 8;
|
||||
);
|
||||
const auto wrapName = [](not_null<History*> history) {
|
||||
const auto name = history->peer->name();
|
||||
return TextWithEntities{
|
||||
return st::wrap_rtl(TextWithEntities{
|
||||
.text = name,
|
||||
.entities = (history->chatListBadgesState().unread
|
||||
? EntitiesInText{
|
||||
@@ -78,7 +78,7 @@ constexpr auto kShowChatNamesCount = 8;
|
||||
{ EntityType::Colorized, 0, int(name.size()), QString() },
|
||||
}
|
||||
: EntitiesInText{}),
|
||||
};
|
||||
});
|
||||
};
|
||||
const auto shown = int(peers.size());
|
||||
const auto accumulated = [&] {
|
||||
|
||||
@@ -189,7 +189,8 @@ rpl::producer<MessagesSlice> HistoryMessagesViewer(
|
||||
};
|
||||
const auto messageId = (aroundId.fullId.msg == ShowAtUnreadMsgId)
|
||||
? computeUnreadAroundId()
|
||||
: (aroundId.fullId.msg == ShowAtTheEndMsgId)
|
||||
: ((aroundId.fullId.msg == ShowAtTheEndMsgId)
|
||||
|| (aroundId == MaxMessagePosition))
|
||||
? (ServerMaxMsgId - 1)
|
||||
: (aroundId.fullId.peer == history->peer->id)
|
||||
? aroundId.fullId.msg
|
||||
|
||||
@@ -471,7 +471,8 @@ GiveawayStart ComputeGiveawayStartData(
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
result.channels.reserve(data.vchannels().v.size());
|
||||
@@ -502,7 +503,8 @@ GiveawayResults ComputeGiveawayResultsData(
|
||||
.additionalPeersCount = additional.value_or_empty(),
|
||||
.winnersCount = data.vwinners_count().v,
|
||||
.unclaimedCount = data.vunclaimed_count().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.refunded = data.is_refunded(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
|
||||
@@ -108,6 +108,7 @@ struct GiveawayStart {
|
||||
TimeId untilDate = 0;
|
||||
int quantity = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
@@ -121,19 +122,21 @@ struct GiveawayResults {
|
||||
int winnersCount = 0;
|
||||
int unclaimedCount = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool refunded = false;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
enum class GiftType : uchar {
|
||||
Premium, // count - months
|
||||
Stars, // count - stars
|
||||
Credits, // count - credits
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
ChannelData *channel = nullptr;
|
||||
int count = 0;
|
||||
int giveawayMsgId = 0;
|
||||
GiftType type = GiftType::Premium;
|
||||
bool viaGiveaway = false;
|
||||
bool unclaimed = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_message_reactions.h"
|
||||
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/application.h"
|
||||
#include "history/history.h"
|
||||
@@ -152,6 +153,10 @@ constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000) + 500;
|
||||
return (i != end(top)) && i->my;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<bool> MaybeAnonymous(uint32 privacySet, uint32 anonymous) {
|
||||
return privacySet ? (anonymous == 1) : std::optional<bool>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PossibleItemReactionsRef LookupPossibleReactions(
|
||||
@@ -261,6 +266,15 @@ PossibleItemReactionsRef LookupPossibleReactions(
|
||||
}
|
||||
result.customAllowed = (allowed.type == AllowedReactionsType::All)
|
||||
&& premiumPossible;
|
||||
|
||||
const auto favoriteId = reactions->favoriteId();
|
||||
if (favoriteId.custom()
|
||||
&& result.customAllowed
|
||||
&& !ranges::contains(result.recent, favoriteId, &Reaction::id)) {
|
||||
if (const auto temp = reactions->lookupTemporary(favoriteId)) {
|
||||
result.recent.insert(begin(result.recent), temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item->reactionsAreTags()) {
|
||||
const auto toFront = [&](Data::ReactionId id) {
|
||||
@@ -1725,6 +1739,7 @@ void Reactions::sendPaidPrivacyRequest(
|
||||
not_null<HistoryItem*> item,
|
||||
PaidReactionSend send) {
|
||||
Expects(!_sendingPaid.contains(item));
|
||||
Expects(send.anonymous.has_value());
|
||||
Expects(!send.count);
|
||||
|
||||
const auto id = item->fullId();
|
||||
@@ -1733,7 +1748,7 @@ void Reactions::sendPaidPrivacyRequest(
|
||||
MTPmessages_TogglePaidReactionPrivacy(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_bool(send.anonymous))
|
||||
MTP_bool(*send.anonymous))
|
||||
).done([=] {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
@@ -1771,7 +1786,8 @@ void Reactions::sendPaidRequest(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id.msg),
|
||||
MTP_int(send.count),
|
||||
MTP_long(randomId)
|
||||
MTP_long(randomId),
|
||||
MTP_bool(send.anonymous.value_or(false))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto item = _owner->message(id)) {
|
||||
if (_sendingPaid.remove(item)) {
|
||||
@@ -1819,9 +1835,13 @@ MessageReactions::~MessageReactions() {
|
||||
cancelScheduledPaid();
|
||||
if (const auto paid = _paid.get()) {
|
||||
if (paid->sending > 0) {
|
||||
finishPaidSending(
|
||||
{ int(paid->sending), (paid->sendingAnonymous == 1) },
|
||||
false);
|
||||
finishPaidSending({
|
||||
.count = int(paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = MaybeAnonymous(
|
||||
paid->sendingPrivacySet,
|
||||
paid->sendingAnonymous),
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2182,7 +2202,9 @@ void MessageReactions::markRead() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageReactions::scheduleSendPaid(int count, bool anonymous) {
|
||||
void MessageReactions::scheduleSendPaid(
|
||||
int count,
|
||||
std::optional<bool> anonymous) {
|
||||
Expects(count >= 0);
|
||||
|
||||
if (!_paid) {
|
||||
@@ -2190,7 +2212,10 @@ void MessageReactions::scheduleSendPaid(int count, bool anonymous) {
|
||||
}
|
||||
_paid->scheduled += count;
|
||||
_paid->scheduledFlag = 1;
|
||||
_paid->scheduledAnonymous = anonymous ? 1 : 0;
|
||||
if (anonymous.has_value()) {
|
||||
_paid->scheduledAnonymous = anonymous.value_or(false) ? 1 : 0;
|
||||
_paid->scheduledPrivacySet = anonymous.has_value();
|
||||
}
|
||||
if (count > 0) {
|
||||
_item->history()->session().credits().lock(count);
|
||||
}
|
||||
@@ -2210,6 +2235,7 @@ void MessageReactions::cancelScheduledPaid() {
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
}
|
||||
if (!_paid->sendingFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
@@ -2224,13 +2250,17 @@ PaidReactionSend MessageReactions::startPaidSending() {
|
||||
_paid->sending = _paid->scheduled;
|
||||
_paid->sendingFlag = _paid->scheduledFlag;
|
||||
_paid->sendingAnonymous = _paid->scheduledAnonymous;
|
||||
_paid->sendingPrivacySet = _paid->scheduledPrivacySet;
|
||||
_paid->scheduled = 0;
|
||||
_paid->scheduledFlag = 0;
|
||||
_paid->scheduledAnonymous = 0;
|
||||
_paid->scheduledPrivacySet = 0;
|
||||
return {
|
||||
.count = int(_paid->sending),
|
||||
.valid = true,
|
||||
.anonymous = (_paid->sendingAnonymous == 1),
|
||||
.anonymous = MaybeAnonymous(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2240,11 +2270,14 @@ void MessageReactions::finishPaidSending(
|
||||
Expects(_paid != nullptr);
|
||||
Expects(send.count == _paid->sending);
|
||||
Expects(send.valid == (_paid->sendingFlag == 1));
|
||||
Expects(send.anonymous == (_paid->sendingAnonymous == 1));
|
||||
Expects(send.anonymous == MaybeAnonymous(
|
||||
_paid->sendingPrivacySet,
|
||||
_paid->sendingAnonymous));
|
||||
|
||||
_paid->sending = 0;
|
||||
_paid->sendingFlag = 0;
|
||||
_paid->sendingAnonymous = 0;
|
||||
_paid->sendingPrivacySet = 0;
|
||||
if (!_paid->scheduledFlag && _paid->top.empty()) {
|
||||
_paid = nullptr;
|
||||
} else if (!send.count) {
|
||||
@@ -2282,12 +2315,13 @@ bool MessageReactions::localPaidAnonymous() const {
|
||||
return !entry.peer;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
const auto api = &_item->history()->session().api();
|
||||
return api->globalPrivacy().paidReactionAnonymousCurrent();
|
||||
};
|
||||
return _paid
|
||||
&& (_paid->scheduledFlag
|
||||
&& ((_paid->scheduledFlag && _paid->scheduledPrivacySet)
|
||||
? (_paid->scheduledAnonymous == 1)
|
||||
: _paid->sendingFlag
|
||||
: (_paid->sendingFlag && _paid->sendingPrivacySet)
|
||||
? (_paid->sendingAnonymous == 1)
|
||||
: minePaidAnonymous());
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ struct MyTagInfo {
|
||||
struct PaidReactionSend {
|
||||
int count = 0;
|
||||
bool valid = false;
|
||||
bool anonymous = false;
|
||||
std::optional<bool> anonymous = false;
|
||||
};
|
||||
|
||||
class Reactions final : private CustomEmojiManager::Listener {
|
||||
@@ -409,7 +409,7 @@ public:
|
||||
[[nodiscard]] bool hasUnread() const;
|
||||
void markRead();
|
||||
|
||||
void scheduleSendPaid(int count, bool anonymous);
|
||||
void scheduleSendPaid(int count, std::optional<bool> anonymous);
|
||||
[[nodiscard]] int scheduledPaid() const;
|
||||
void cancelScheduledPaid();
|
||||
|
||||
@@ -424,12 +424,14 @@ public:
|
||||
private:
|
||||
struct Paid {
|
||||
std::vector<TopPaid> top;
|
||||
uint32 scheduled: 30 = 0;
|
||||
uint32 scheduled: 29 = 0;
|
||||
uint32 scheduledFlag : 1 = 0;
|
||||
uint32 scheduledAnonymous : 1 = 0;
|
||||
uint32 sending : 30 = 0;
|
||||
uint32 scheduledPrivacySet : 1 = 0;
|
||||
uint32 sending : 29 = 0;
|
||||
uint32 sendingFlag : 1 = 0;
|
||||
uint32 sendingAnonymous : 1 = 0;
|
||||
uint32 sendingPrivacySet : 1 = 0;
|
||||
};
|
||||
const not_null<HistoryItem*> _item;
|
||||
|
||||
|
||||
@@ -437,11 +437,12 @@ InMemoryKey PeerData::userpicUniqueKey(Ui::PeerUserpicView &view) const {
|
||||
: inMemoryKey(_userpic.location());
|
||||
}
|
||||
|
||||
QImage PeerData::generateUserpicImage(
|
||||
QImage PeerData::GenerateUserpicImage(
|
||||
not_null<PeerData*> peer,
|
||||
Ui::PeerUserpicView &view,
|
||||
int size,
|
||||
std::optional<int> radius) const {
|
||||
if (const auto userpic = userpicCloudImage(view)) {
|
||||
std::optional<int> radius) {
|
||||
if (const auto userpic = peer->userpicCloudImage(view)) {
|
||||
auto image = userpic->scaled(
|
||||
{ size, size },
|
||||
Qt::IgnoreAspectRatio,
|
||||
@@ -455,7 +456,7 @@ QImage PeerData::generateUserpicImage(
|
||||
return image;
|
||||
} else if (radius) {
|
||||
return round(*radius);
|
||||
} else if (isForum()) {
|
||||
} else if (peer->isForum()) {
|
||||
return round(size * Ui::ForumUserpicRadiusMultiplier());
|
||||
} else {
|
||||
return Images::Circle(std::move(image));
|
||||
@@ -468,11 +469,12 @@ QImage PeerData::generateUserpicImage(
|
||||
|
||||
Painter p(&result);
|
||||
if (radius == 0) {
|
||||
ensureEmptyUserpic()->paintSquare(p, 0, 0, size, size);
|
||||
peer->ensureEmptyUserpic()->paintSquare(p, 0, 0, size, size);
|
||||
} else if (radius) {
|
||||
ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size, *radius);
|
||||
} else if (isForum()) {
|
||||
ensureEmptyUserpic()->paintRounded(
|
||||
const auto r = *radius;
|
||||
peer->ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size, r);
|
||||
} else if (peer->isForum()) {
|
||||
peer->ensureEmptyUserpic()->paintRounded(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
@@ -480,7 +482,7 @@ QImage PeerData::generateUserpicImage(
|
||||
size,
|
||||
size * Ui::ForumUserpicRadiusMultiplier());
|
||||
} else {
|
||||
ensureEmptyUserpic()->paintCircle(p, 0, 0, size, size);
|
||||
peer->ensureEmptyUserpic()->paintCircle(p, 0, 0, size, size);
|
||||
}
|
||||
p.end();
|
||||
|
||||
|
||||
@@ -333,10 +333,11 @@ public:
|
||||
[[nodiscard]] Ui::PeerUserpicView createUserpicView();
|
||||
[[nodiscard]] bool useEmptyUserpic(Ui::PeerUserpicView &view) const;
|
||||
[[nodiscard]] InMemoryKey userpicUniqueKey(Ui::PeerUserpicView &view) const;
|
||||
[[nodiscard]] QImage generateUserpicImage(
|
||||
[[nodiscard]] static QImage GenerateUserpicImage(
|
||||
not_null<PeerData*> peer,
|
||||
Ui::PeerUserpicView &view,
|
||||
int size,
|
||||
std::optional<int> radius = {}) const;
|
||||
std::optional<int> radius = {});
|
||||
[[nodiscard]] ImageLocation userpicLocation() const;
|
||||
|
||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
@@ -542,10 +542,12 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
||||
}
|
||||
state->key = key;
|
||||
state->empty = false;
|
||||
consumer.put_next(peer->generateUserpicImage(
|
||||
state->view,
|
||||
size,
|
||||
radius));
|
||||
consumer.put_next(
|
||||
PeerData::GenerateUserpicImage(
|
||||
peer,
|
||||
state->view,
|
||||
size,
|
||||
radius));
|
||||
};
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
|
||||
@@ -118,7 +118,7 @@ constexpr auto kBlurRadius = 24;
|
||||
const auto &partSize = partRect.width();
|
||||
const auto partSkip = fullSize - partSize;
|
||||
auto result = Images::Circle(BlurredDarkenedPart(
|
||||
peer->generateUserpicImage(view, fullSize * ratio, 0),
|
||||
PeerData::GenerateUserpicImage(peer, view, fullSize * ratio, 0),
|
||||
QRect(
|
||||
QPoint(partSkip, partSkip) * ratio,
|
||||
QSize(partSize, partSize) * ratio)));
|
||||
|
||||
@@ -3326,7 +3326,9 @@ void Widget::updateControlsGeometry() {
|
||||
}
|
||||
|
||||
const auto wasScrollTop = _scroll->scrollTop();
|
||||
const auto newScrollTop = (_topDelta < 0 && wasScrollTop <= 0)
|
||||
const auto newScrollTop = (wasScrollTop == 0)
|
||||
? wasScrollTop
|
||||
: (_topDelta < 0 && wasScrollTop <= 0)
|
||||
? wasScrollTop
|
||||
: (wasScrollTop + _topDelta);
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ private:
|
||||
class PopularAppsController final
|
||||
: public Suggestions::ObjectListController {
|
||||
public:
|
||||
explicit PopularAppsController(
|
||||
PopularAppsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
Fn<bool(not_null<PeerData*>)> filterOut,
|
||||
rpl::producer<> filterOutRefreshes);
|
||||
@@ -1127,7 +1127,9 @@ PopularAppsController::PopularAppsController(
|
||||
}
|
||||
|
||||
void PopularAppsController::prepare() {
|
||||
setupPlainDivider(tr::lng_bot_apps_popular());
|
||||
if (_filterOut) {
|
||||
setupPlainDivider(tr::lng_bot_apps_popular());
|
||||
}
|
||||
rpl::single() | rpl::then(
|
||||
std::move(_filterOutRefreshes)
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1163,10 +1165,11 @@ void PopularAppsController::fill() {
|
||||
|
||||
void PopularAppsController::appendRow(not_null<UserData*> bot) {
|
||||
auto row = std::make_unique<PeerListRow>(bot);
|
||||
//if (const auto count = bot->botInfo->activeUsers) {
|
||||
// row->setCustomStatus(
|
||||
// tr::lng_bot_status_users(tr::now, lt_count_decimal, count));
|
||||
//}
|
||||
if (bot->isBot()) {
|
||||
if (!bot->botInfo->activeUsers && !bot->username().isEmpty()) {
|
||||
row->setCustomStatus('@' + bot->username());
|
||||
}
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
|
||||
@@ -2283,4 +2286,40 @@ RecentPeersList RecentPeersContent(not_null<Main::Session*> session) {
|
||||
return RecentPeersList{ session->recentPeers().list() };
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> StarsExamplesBox(
|
||||
not_null<Window::SessionController*> window) {
|
||||
auto controller = std::make_unique<PopularAppsController>(
|
||||
window,
|
||||
nullptr,
|
||||
nullptr);
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setTitle(tr::lng_credits_box_history_entry_gift_examples());
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
raw->load();
|
||||
raw->chosen() | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
if (info->hasMainApp) {
|
||||
window->session().attachWebView().open({
|
||||
.bot = user,
|
||||
.context = {
|
||||
.controller = window,
|
||||
.maySkipConfirmation = true,
|
||||
},
|
||||
.source = InlineBots::WebViewSourceBotProfile(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window->showPeerInfo(peer);
|
||||
}, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -23,6 +23,7 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class ElasticScroll;
|
||||
class SettingsSlider;
|
||||
class VerticalLayout;
|
||||
@@ -215,4 +216,7 @@ private:
|
||||
[[nodiscard]] RecentPeersList RecentPeersContent(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> StarsExamplesBox(
|
||||
not_null<Window::SessionController*> window);
|
||||
|
||||
} // namespace Dialogs
|
||||
|
||||
@@ -733,12 +733,47 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
|
||||
GiveawayStart ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
|
||||
auto result = GiveawayStart{
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.quantity = data.vquantity().v,
|
||||
.months = data.vmonths().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
for (const auto &id : data.vchannels().v) {
|
||||
result.channels.push_back(ChannelId(id));
|
||||
}
|
||||
if (const auto countries = data.vcountries_iso2()) {
|
||||
result.countries.reserve(countries->v.size());
|
||||
for (const auto &country : countries->v) {
|
||||
result.countries.push_back(qs(country));
|
||||
}
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GiveawayResults ParseGiveaway(const MTPDmessageMediaGiveawayResults &data) {
|
||||
const auto additional = data.vadditional_peers_count();
|
||||
auto result = GiveawayResults{
|
||||
.channel = ChannelId(data.vchannel_id()),
|
||||
.untilDate = data.vuntil_date().v,
|
||||
.launchId = data.vlaunch_msg_id().v,
|
||||
.additionalPeersCount = additional.value_or_empty(),
|
||||
.winnersCount = data.vwinners_count().v,
|
||||
.unclaimedCount = data.vunclaimed_count().v,
|
||||
.months = data.vmonths().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.refunded = data.is_refunded(),
|
||||
.all = !data.is_only_new_subscribers(),
|
||||
};
|
||||
result.winners.reserve(data.vwinners().v.size());
|
||||
for (const auto &id : data.vwinners().v) {
|
||||
result.winners.push_back(UserId(id));
|
||||
}
|
||||
if (const auto additional = data.vprize_description()) {
|
||||
result.additionalPrize = qs(*additional);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1246,7 +1281,7 @@ Media ParseMedia(
|
||||
}, [&](const MTPDmessageMediaGiveaway &data) {
|
||||
result.content = ParseGiveaway(data);
|
||||
}, [&](const MTPDmessageMediaGiveawayResults &data) {
|
||||
// #TODO export giveaway
|
||||
result.content = ParseGiveaway(data);
|
||||
}, [&](const MTPDmessageMediaPaidMedia &data) {
|
||||
result.content = ParsePaidMedia(context, data, folder, date);
|
||||
}, [](const MTPDmessageMediaEmpty &data) {});
|
||||
@@ -1508,6 +1543,7 @@ ServiceAction ParseServiceAction(
|
||||
auto content = ActionGiveawayResults();
|
||||
content.winners = data.vwinners_count().v;
|
||||
content.unclaimed = data.vunclaimed_count().v;
|
||||
content.credits = data.is_stars();
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionBoostApply &data) {
|
||||
auto content = ActionBoostApply();
|
||||
@@ -1527,8 +1563,16 @@ ServiceAction ParseServiceAction(
|
||||
content.cost = Ui::FillAmountAndCurrency(
|
||||
data.vamount().v,
|
||||
qs(data.vcurrency())).toUtf8();
|
||||
content.stars = data.vstars().v;
|
||||
content.credits = data.vstars().v;
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionPrizeStars &data) {
|
||||
result.content = ActionPrizeStars{
|
||||
.peerId = ParsePeerId(data.vboost_peer()),
|
||||
.amount = data.vstars().v,
|
||||
.transactionId = data.vtransaction_id().v,
|
||||
.giveawayMsgId = data.vgiveaway_msg_id().v,
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
};
|
||||
}, [](const MTPDmessageActionEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -210,10 +210,29 @@ struct Poll {
|
||||
};
|
||||
|
||||
struct GiveawayStart {
|
||||
std::vector<QString> countries;
|
||||
std::vector<ChannelId> channels;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
uint64 credits = 0;
|
||||
int quantity = 0;
|
||||
int months = 0;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct GiveawayResults {
|
||||
ChannelId channel = 0;
|
||||
std::vector<PeerId> winners;
|
||||
QString additionalPrize;
|
||||
TimeId untilDate = 0;
|
||||
int32 launchId = 0;
|
||||
int additionalPeersCount = 0;
|
||||
int winnersCount = 0;
|
||||
int unclaimedCount = 0;
|
||||
int months = 0;
|
||||
uint64 credits = 0;
|
||||
bool refunded = false;
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
struct UserpicsSlice {
|
||||
@@ -349,6 +368,7 @@ struct Media {
|
||||
Invoice,
|
||||
Poll,
|
||||
GiveawayStart,
|
||||
GiveawayResults,
|
||||
PaidMedia,
|
||||
UnsupportedMedia> content;
|
||||
TimeId ttl = 0;
|
||||
@@ -570,6 +590,7 @@ struct ActionGiveawayLaunch {
|
||||
struct ActionGiveawayResults {
|
||||
int winners = 0;
|
||||
int unclaimed = 0;
|
||||
bool credits = false;
|
||||
};
|
||||
|
||||
struct ActionBoostApply {
|
||||
@@ -585,7 +606,15 @@ struct ActionPaymentRefunded {
|
||||
|
||||
struct ActionGiftStars {
|
||||
Utf8String cost;
|
||||
int stars = 0;
|
||||
int credits = 0;
|
||||
};
|
||||
|
||||
struct ActionPrizeStars {
|
||||
PeerId peerId = 0;
|
||||
uint64 amount = 0;
|
||||
Utf8String transactionId;
|
||||
int32 giveawayMsgId = 0;
|
||||
bool isUnclaimed = false;
|
||||
};
|
||||
|
||||
struct ServiceAction {
|
||||
@@ -631,7 +660,8 @@ struct ServiceAction {
|
||||
ActionGiveawayResults,
|
||||
ActionBoostApply,
|
||||
ActionPaymentRefunded,
|
||||
ActionGiftStars> content;
|
||||
ActionGiftStars,
|
||||
ActionPrizeStars> content;
|
||||
};
|
||||
|
||||
ServiceAction ParseServiceAction(
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/output/export_output_html.h"
|
||||
|
||||
#include "countries/countries_instance.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
#include "core/utils.h"
|
||||
@@ -597,7 +598,8 @@ private:
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain);
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink);
|
||||
[[nodiscard]] QByteArray pushGenericMedia(const MediaData &data);
|
||||
[[nodiscard]] QByteArray pushStickerMedia(
|
||||
const Data::Document &data,
|
||||
@@ -615,6 +617,10 @@ private:
|
||||
[[nodiscard]] QByteArray pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayStart &data);
|
||||
[[nodiscard]] QByteArray pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayResults &data,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink);
|
||||
|
||||
File _file;
|
||||
QByteArray _composedStart;
|
||||
@@ -1307,9 +1313,20 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
return serviceFrom + " just started a giveaway "
|
||||
"of Telegram Premium subscriptions to its followers.";
|
||||
}, [&](const ActionGiveawayResults &data) {
|
||||
return QByteArray::number(data.winners)
|
||||
+ " of the giveaway were randomly selected by Telegram "
|
||||
"and received private messages with giftcodes.";
|
||||
return !data.winners
|
||||
? "No winners of the giveaway could be selected."
|
||||
: (data.credits && data.unclaimed)
|
||||
? "Some winners of the giveaway were randomly selected by "
|
||||
"Telegram and received their prize."
|
||||
: (!data.credits && data.unclaimed)
|
||||
? "Some winners of the giveaway were randomly selected by "
|
||||
"Telegram and received private messages with giftcodes."
|
||||
: (data.credits && !data.unclaimed)
|
||||
? NumberToString(data.winners) + " of the giveaway was randomly "
|
||||
"selected by Telegram and received their prize."
|
||||
: NumberToString(data.winners) + " of the giveaway was randomly "
|
||||
"selected by Telegram and received private messages with "
|
||||
"giftcodes.";
|
||||
}, [&](const ActionBoostApply &data) {
|
||||
return serviceFrom
|
||||
+ " boosted the group "
|
||||
@@ -1322,14 +1339,20 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
+ amount;
|
||||
return result;
|
||||
}, [&](const ActionGiftStars &data) {
|
||||
if (!data.stars || data.cost.isEmpty()) {
|
||||
if (!data.credits || data.cost.isEmpty()) {
|
||||
return serviceFrom + " sent you a gift.";
|
||||
}
|
||||
return serviceFrom
|
||||
+ " sent you a gift for "
|
||||
+ data.cost
|
||||
+ ": "
|
||||
+ QString::number(data.stars).toUtf8()
|
||||
+ QString::number(data.credits).toUtf8()
|
||||
+ " Telegram Stars.";
|
||||
}, [&](const ActionPrizeStars &data) {
|
||||
return "You won a prize in a giveaway organized by "
|
||||
+ peers.wrapPeerName(data.peerId)
|
||||
+ ".\n Your prize is "
|
||||
+ QString::number(data.amount).toUtf8()
|
||||
+ " Telegram Stars.";
|
||||
}, [](v::null_t) { return QByteArray(); });
|
||||
|
||||
@@ -1451,7 +1474,13 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
block.append(popTag());
|
||||
}
|
||||
|
||||
block.append(pushMedia(message, basePath, peers, internalLinksDomain));
|
||||
block.append(
|
||||
pushMedia(
|
||||
message,
|
||||
basePath,
|
||||
peers,
|
||||
internalLinksDomain,
|
||||
wrapMessageLink));
|
||||
|
||||
const auto text = FormatText(message.text, internalLinksDomain, _base);
|
||||
if (!text.isEmpty()) {
|
||||
@@ -1554,7 +1583,8 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain) {
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink) {
|
||||
const auto data = prepareMediaData(
|
||||
message,
|
||||
basePath,
|
||||
@@ -1582,6 +1612,8 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||
return pushPoll(*poll);
|
||||
} else if (const auto giveaway = std::get_if<GiveawayStart>(&content)) {
|
||||
return pushGiveaway(peers, *giveaway);
|
||||
} else if (const auto giveaway = std::get_if<GiveawayResults>(&content)) {
|
||||
return pushGiveaway(peers, *giveaway, wrapMessageLink);
|
||||
}
|
||||
Assert(v::is_null(content));
|
||||
return QByteArray();
|
||||
@@ -1910,17 +1942,47 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
result.append(pushDiv("media_giveaway"));
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("Giveaway Prizes"));
|
||||
result.append((data.quantity > 1)
|
||||
? SerializeString("Giveaway Prizes")
|
||||
: SerializeString("Giveaway Prize"));
|
||||
result.append(popTag());
|
||||
|
||||
{
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString(data.additionalPrize.toUtf8()));
|
||||
result.append(popTag());
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("with"));
|
||||
result.append(popTag());
|
||||
};
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString((data.quantity > 1)
|
||||
? "Telegram Premium Subscriptions"
|
||||
: "Telegram Premium Subscription")
|
||||
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
|
||||
+ (data.months > 1 ? "months." : "month."));
|
||||
if (data.credits > 0) {
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ (SerializeString(data.credits == 1 ? (" Star") : (" Stars")))
|
||||
+ "</b> " + SerializeString("will be distributed ")
|
||||
+ ((data.quantity == 1)
|
||||
? SerializeString("to ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> " + SerializeString("winner.")
|
||||
: SerializeString("among ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> " + SerializeString("winners.")));
|
||||
} else {
|
||||
result.append("<b>"
|
||||
+ Data::NumberToString(data.quantity)
|
||||
+ "</b> "
|
||||
+ SerializeString((data.quantity > 1)
|
||||
? "Telegram Premium Subscriptions"
|
||||
: "Telegram Premium Subscription")
|
||||
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
|
||||
+ (data.months > 1 ? "months." : "month."));
|
||||
}
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
@@ -1928,15 +1990,85 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
result.append(popTag());
|
||||
result.append(pushDiv("section_body"));
|
||||
auto channels = QByteArrayList();
|
||||
auto anyChannel = false;
|
||||
auto anyGroup = false;
|
||||
for (const auto &channel : data.channels) {
|
||||
if (const auto chat = peers.peer(channel).chat()) {
|
||||
if (chat->isBroadcast) {
|
||||
anyChannel = true;
|
||||
} else if (chat->isSupergroup) {
|
||||
anyGroup = true;
|
||||
}
|
||||
}
|
||||
channels.append("<b>" + peers.wrapPeerName(channel) + "</b>");
|
||||
}
|
||||
result.append(SerializeString((channels.size() > 1)
|
||||
? "All subscribers of those channels: "
|
||||
: "All subscribers of the channel: ")
|
||||
+ channels.join(", "));
|
||||
|
||||
const auto participants = [&] {
|
||||
if (data.all && !anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All subscribers of the channel:";
|
||||
}
|
||||
if (data.all && !anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All subscribers of the channels:";
|
||||
}
|
||||
if (data.all && anyGroup && !anyChannel && channels.size() == 1) {
|
||||
return "All members of the group:";
|
||||
}
|
||||
if (data.all && anyGroup && !anyChannel && channels.size() > 1) {
|
||||
return "All members of the groups:";
|
||||
}
|
||||
if (data.all && anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All members of the group:";
|
||||
}
|
||||
if (data.all && anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All members of the groups and channels:";
|
||||
}
|
||||
if (!data.all && !anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the channel below after this date:";
|
||||
}
|
||||
if (!data.all && !anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the channels below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && !anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the group below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && !anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the groups below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && anyChannel && channels.size() == 1) {
|
||||
return "All users who joined the group below after this date:";
|
||||
}
|
||||
if (!data.all && anyGroup && anyChannel && channels.size() > 1) {
|
||||
return "All users who joined the groups and channels below "
|
||||
"after this date:";
|
||||
}
|
||||
return "";
|
||||
}();
|
||||
|
||||
result.append(SerializeString(participants)) + channels.join(", ");
|
||||
result.append(popTag());
|
||||
|
||||
{
|
||||
const auto &instance = Countries::Instance();
|
||||
auto countries = QStringList();
|
||||
for (const auto &country : data.countries) {
|
||||
const auto name = instance.countryNameByISO2(country);
|
||||
const auto flag = instance.flagEmojiByISO2(country);
|
||||
countries.push_back(flag + QChar(0xA0) + name);
|
||||
}
|
||||
|
||||
if (const auto count = countries.size()) {
|
||||
auto united = countries.front();
|
||||
for (auto i = 1; i != count; ++i) {
|
||||
united = ((i + 1 == count)
|
||||
? u"%1 and %2"_q
|
||||
: u"%1, %2"_q).arg(united, countries[i]);
|
||||
}
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append(
|
||||
SerializeString((u"from %1"_q).arg(united).toUtf8()));
|
||||
result.append(popTag());
|
||||
}
|
||||
}
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append(SerializeString("Winners Selection Date"));
|
||||
result.append(popTag());
|
||||
@@ -1949,6 +2081,85 @@ QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||
const PeersMap &peers,
|
||||
const Data::GiveawayResults &data,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink) {
|
||||
auto result = pushDiv("media_wrap clearfix");
|
||||
result.append(pushDiv("media_giveaway"));
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append((data.winnersCount > 1)
|
||||
? SerializeString("Winners Selected!")
|
||||
: SerializeString("Winner Selected!"));
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
result.append(
|
||||
"<b>" + Data::NumberToString(data.winnersCount) + "</b> "
|
||||
+ SerializeString((data.winnersCount > 1) ? "winners" : "winner")
|
||||
+ " of the "
|
||||
+ wrapMessageLink(data.launchId, "Giveaway")
|
||||
+ " was randomly selected by Telegram.");
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_title bold"));
|
||||
result.append((data.winnersCount > 1)
|
||||
? SerializeString("Winners")
|
||||
: SerializeString("Winner"));
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
auto winners = QByteArrayList();
|
||||
for (const auto &winner : data.winners) {
|
||||
winners.append("<b>" + peers.wrapPeerName(winner) + "</b>");
|
||||
}
|
||||
const auto andMore = [&, size = data.winners.size()] {
|
||||
if (data.winnersCount > size) {
|
||||
return SerializeString(" and ")
|
||||
+ Data::NumberToString(data.winnersCount - size)
|
||||
+ SerializeString(" more!");
|
||||
}
|
||||
return QByteArray();
|
||||
}();
|
||||
result.append(winners.join(", ") + andMore);
|
||||
result.append(popTag());
|
||||
|
||||
result.append(pushDiv("section_body"));
|
||||
const auto prize = [&, singleStar = (data.credits == 1)] {
|
||||
if (data.credits && data.winnersCount == 1) {
|
||||
return SerializeString("The winner received ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ "</b>"
|
||||
+ SerializeString(singleStar ? " Star." : " Stars.");
|
||||
} else if (data.credits && data.winnersCount > 1) {
|
||||
return SerializeString("All winners received ")
|
||||
+ "<b>"
|
||||
+ Data::NumberToString(data.credits)
|
||||
+ "</b>"
|
||||
+ SerializeString(singleStar
|
||||
? " Star in total."
|
||||
: " Stars in total.");
|
||||
} else if (data.unclaimedCount) {
|
||||
return SerializeString("Some winners couldn't be selected.");
|
||||
} else if (data.winnersCount == 1) {
|
||||
return SerializeString(
|
||||
"The winner received their gift link in a private message.");
|
||||
} else if (data.winnersCount > 1) {
|
||||
return SerializeString(
|
||||
"All winners received gift links in private messages.");
|
||||
}
|
||||
return QByteArray();
|
||||
}();
|
||||
result.append(prize);
|
||||
result.append(popTag());
|
||||
|
||||
result.append(popTag());
|
||||
result.append(popTag());
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||
const Data::Message &message,
|
||||
const QString &basePath,
|
||||
@@ -2108,6 +2319,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
|
||||
}, [](const Poll &data) {
|
||||
}, [](const GiveawayStart &data) {
|
||||
}, [](const GiveawayResults &data) {
|
||||
}, [&](const PaidMedia &data) {
|
||||
result.classes = "media_invoice";
|
||||
result.status = Data::FormatMoneyAmount(data.stars, "XTR");
|
||||
|
||||
@@ -615,6 +615,7 @@ QByteArray SerializeMessage(
|
||||
pushAction("giveaway_results");
|
||||
push("winners", data.winners);
|
||||
push("unclaimed", data.unclaimed);
|
||||
push("stars", data.credits);
|
||||
}, [&](const ActionSetChatWallPaper &data) {
|
||||
pushActor();
|
||||
pushAction(data.same
|
||||
@@ -638,9 +639,18 @@ QByteArray SerializeMessage(
|
||||
if (!data.cost.isEmpty()) {
|
||||
push("cost", data.cost);
|
||||
}
|
||||
if (data.stars) {
|
||||
push("stars", data.stars);
|
||||
if (data.credits) {
|
||||
push("stars", data.credits);
|
||||
}
|
||||
}, [&](const ActionPrizeStars &data) {
|
||||
pushActor();
|
||||
pushAction("stars_prize");
|
||||
push("boost_peer_id", data.peerId);
|
||||
pushBare("boost_peer_name", wrapPeerName(data.peerId));
|
||||
push("stars", data.amount);
|
||||
push("is_unclaimed", data.isUnclaimed);
|
||||
push("giveaway_msg_id", data.giveawayMsgId);
|
||||
push("transaction_id", data.transactionId);
|
||||
}, [](v::null_t) {});
|
||||
|
||||
if (v::is_null(message.action.content)) {
|
||||
@@ -780,7 +790,7 @@ QByteArray SerializeMessage(
|
||||
{ "answers", serialized }
|
||||
}));
|
||||
}, [&](const GiveawayStart &data) {
|
||||
context.nesting.push_back(Context::kObject);
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto channels = ranges::views::all(
|
||||
data.channels
|
||||
) | ranges::views::transform([&](ChannelId id) {
|
||||
@@ -789,11 +799,53 @@ QByteArray SerializeMessage(
|
||||
const auto serialized = SerializeArray(context, channels);
|
||||
context.nesting.pop_back();
|
||||
|
||||
push("giveaway_information", SerializeObject(context, {
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto countries = ranges::views::all(
|
||||
data.countries
|
||||
) | ranges::views::transform([&](const QString &code) {
|
||||
return SerializeString(code.toUtf8());
|
||||
}) | ranges::to_vector;
|
||||
const auto serializedCountries = SerializeArray(context, countries);
|
||||
context.nesting.pop_back();
|
||||
|
||||
const auto additionalPrize = data.additionalPrize.toUtf8();
|
||||
|
||||
pushBare("giveaway_information", SerializeObject(context, {
|
||||
{ "quantity", NumberToString(data.quantity) },
|
||||
{ "months", NumberToString(data.months) },
|
||||
{ "until_date", SerializeDate(data.untilDate) },
|
||||
{ "channels", serialized },
|
||||
{ "countries", serializedCountries },
|
||||
{ "additional_prize", SerializeString(additionalPrize) },
|
||||
{ "stars", NumberToString(data.credits) },
|
||||
{ "is_only_new_subscribers", (!data.all) ? "true" : "false" },
|
||||
}));
|
||||
}, [&](const GiveawayResults &data) {
|
||||
context.nesting.push_back(Context::kArray);
|
||||
const auto winners = ranges::views::all(
|
||||
data.winners
|
||||
) | ranges::views::transform([&](PeerId id) {
|
||||
return NumberToString(id.value);
|
||||
}) | ranges::to_vector;
|
||||
const auto serialized = SerializeArray(context, winners);
|
||||
context.nesting.pop_back();
|
||||
|
||||
const auto additionalPrize = data.additionalPrize.toUtf8();
|
||||
const auto peersCount = data.additionalPeersCount;
|
||||
|
||||
pushBare("giveaway_results", SerializeObject(context, {
|
||||
{ "channel", NumberToString(data.channel.bare) },
|
||||
{ "winners", serialized },
|
||||
{ "additional_prize", SerializeString(additionalPrize) },
|
||||
{ "until_date", SerializeDate(data.untilDate) },
|
||||
{ "launch_message_id", NumberToString(data.launchId) },
|
||||
{ "additional_peers_count", NumberToString(peersCount) },
|
||||
{ "winners_count", NumberToString(data.winnersCount) },
|
||||
{ "unclaimed_count", NumberToString(data.unclaimedCount) },
|
||||
{ "months", NumberToString(data.months) },
|
||||
{ "stars", NumberToString(data.credits) },
|
||||
{ "is_refunded", data.refunded ? "true" : "false" },
|
||||
{ "is_only_new_subscribers", (!data.all) ? "true" : "false" },
|
||||
}));
|
||||
}, [&](const PaidMedia &data) {
|
||||
push("paid_stars_amount", data.stars);
|
||||
|
||||
@@ -70,14 +70,18 @@ exportCancelButton: RoundButton(attentionBoxButton) {
|
||||
width: 200px;
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(semibold 15px);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(semibold 15px);
|
||||
}
|
||||
}
|
||||
exportCancelBottom: 30px;
|
||||
exportDoneButton: RoundButton(defaultActiveButton) {
|
||||
width: 200px;
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(semibold 15px);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(semibold 15px);
|
||||
}
|
||||
}
|
||||
|
||||
exportAboutLabel: FlatLabel(boxLabel) {
|
||||
|
||||
@@ -362,9 +362,9 @@ void ProgressWidget::showDone() {
|
||||
tr::lng_export_done(),
|
||||
st::exportDoneButton);
|
||||
const auto desired = std::min(
|
||||
st::exportDoneButton.font->width(tr::lng_export_done(tr::now))
|
||||
st::exportDoneButton.style.font->width(tr::lng_export_done(tr::now))
|
||||
+ st::exportDoneButton.height
|
||||
- st::exportDoneButton.font->height,
|
||||
- st::exportDoneButton.style.font->height,
|
||||
st::exportPanelSize.width() - 2 * st::exportCancelBottom);
|
||||
if (_done->width() < desired) {
|
||||
_done->setFullWidth(desired);
|
||||
|
||||
@@ -50,6 +50,10 @@ EditFlagsDescriptor<FilterValue::Flags> FilterValueLabels(bool isChannel) {
|
||||
? tr::lng_admin_log_filter_voice_chats
|
||||
: tr::lng_admin_log_filter_voice_chats_channel)(tr::now),
|
||||
},
|
||||
{
|
||||
Flag::SubExtend,
|
||||
tr::lng_admin_log_filter_sub_extend(tr::now),
|
||||
},
|
||||
};
|
||||
if (!isChannel) {
|
||||
settings.push_back({
|
||||
|
||||
@@ -28,8 +28,9 @@ struct FilterValue final {
|
||||
GroupCall = (1U << 14),
|
||||
Invites = (1U << 15),
|
||||
Topics = (1U << 16),
|
||||
SubExtend = (1U << 17),
|
||||
|
||||
MAX_FIELD = (1U << 16),
|
||||
MAX_FIELD = (1U << 17),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
@@ -39,6 +39,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/expandable_peer_list.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -52,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
@@ -569,11 +572,13 @@ void InnerWidget::showFilter(Fn<void(FilterValue &&filter)> callback) {
|
||||
const auto users = ranges::views::all(
|
||||
peers
|
||||
) | ranges::views::transform([](not_null<PeerData*> p) {
|
||||
return not_null{ p->asUser() };
|
||||
return not_null{ p->asUser() };
|
||||
}) | ranges::to_vector;
|
||||
callback(FilterValue{
|
||||
.flags = collectFlags(),
|
||||
.admins = users,
|
||||
.admins = (admins.size() == users.size())
|
||||
? std::nullopt
|
||||
: std::optional(users),
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
|
||||
@@ -832,7 +837,8 @@ void InnerWidget::preloadMore(Direction direction) {
|
||||
| ((f & LocalFlag::Delete) ? Flag::f_delete : empty)
|
||||
| ((f & LocalFlag::GroupCall) ? Flag::f_group_call : empty)
|
||||
| ((f & LocalFlag::Invites) ? Flag::f_invites : empty)
|
||||
| ((f & LocalFlag::Topics) ? Flag::f_forums : empty);
|
||||
| ((f & LocalFlag::Topics) ? Flag::f_forums : empty)
|
||||
| ((f & LocalFlag::SubExtend) ? Flag::f_sub_extend : empty);
|
||||
}();
|
||||
if (_filter.flags != 0) {
|
||||
flags |= MTPchannels_GetAdminLog::Flag::f_events_filter;
|
||||
@@ -1503,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();
|
||||
@@ -1538,32 +1557,57 @@ 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);
|
||||
|
||||
{
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
auto handler = [=, this] {
|
||||
participant->session().changes().peerUpdates(
|
||||
_channel,
|
||||
Data::PeerUpdate::Flag::Members
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
_downLoaded = false;
|
||||
preloadMore(Direction::Down);
|
||||
lifetime->destroy();
|
||||
}, *lifetime);
|
||||
participant->session().api().chatParticipants().kick(
|
||||
_channel,
|
||||
participant,
|
||||
{ _channel->restrictions(), 0 });
|
||||
};
|
||||
Ui::Menu::CreateAddActionCallback(_menu)({
|
||||
.text = tr::lng_context_ban_user(tr::now),
|
||||
.handler = std::move(handler),
|
||||
.icon = &st::menuIconBlockAttention,
|
||||
.isAttention = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::restrictParticipant(
|
||||
|
||||
@@ -780,6 +780,7 @@ void GenerateItems(
|
||||
using LogChangeWallpaper = MTPDchannelAdminLogEventActionChangeWallpaper;
|
||||
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
|
||||
using LogToggleSignatureProfiles = MTPDchannelAdminLogEventActionToggleSignatureProfiles;
|
||||
using LogParticipantSubExtend = MTPDchannelAdminLogEventActionParticipantSubExtend;
|
||||
|
||||
const auto session = &history->session();
|
||||
const auto id = event.vid().v;
|
||||
@@ -2037,6 +2038,31 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
const auto createParticipantSubExtend = [&](const LogParticipantSubExtend &action) {
|
||||
const auto participant = Api::ChatParticipant(
|
||||
action.vnew_participant(),
|
||||
channel);
|
||||
if (!participant.subscriptionDate()) {
|
||||
return;
|
||||
}
|
||||
const auto participantPeer = channel->owner().peer(participant.id());
|
||||
const auto participantPeerLink = participantPeer->createOpenLink();
|
||||
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(parsed) },
|
||||
Ui::Text::WithEntities),
|
||||
participantPeerLink);
|
||||
};
|
||||
|
||||
action.match(
|
||||
createChangeTitle,
|
||||
createChangeAbout,
|
||||
@@ -2086,7 +2112,8 @@ void GenerateItems(
|
||||
createChangeProfilePeerColor,
|
||||
createChangeWallpaper,
|
||||
createChangeEmojiStatus,
|
||||
createToggleSignatureProfiles);
|
||||
createToggleSignatureProfiles,
|
||||
createParticipantSubExtend);
|
||||
}
|
||||
|
||||
} // namespace AdminLog
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_text.h"
|
||||
#include "history/history_view_swipe.h"
|
||||
#include "payments/payments_reaction_process.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||
@@ -123,6 +124,15 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
|
||||
return start;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CanSendReply(not_null<const HistoryItem*> item) {
|
||||
const auto peer = item->history()->peer;
|
||||
const auto topic = item->topic();
|
||||
return topic
|
||||
? Data::CanSendAnything(topic)
|
||||
: (Data::CanSendAnything(peer)
|
||||
&& (!peer->isChannel() || peer->asChannel()->amIn()));
|
||||
}
|
||||
|
||||
void FillSponsoredMessagesMenu(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
@@ -485,6 +495,7 @@ HistoryInner::HistoryInner(
|
||||
}, _scroll->lifetime());
|
||||
|
||||
setupSharingDisallowed();
|
||||
setupSwipeReply();
|
||||
}
|
||||
|
||||
void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
||||
@@ -567,6 +578,75 @@ void HistoryInner::setupSharingDisallowed() {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void HistoryInner::setupSwipeReply() {
|
||||
if (_peer && _peer->isChannel() && !_peer->isMegagroup()) {
|
||||
return;
|
||||
}
|
||||
HistoryView::SetupSwipeHandler(this, _scroll, [=, history = _history](
|
||||
HistoryView::ChatPaintGestureHorizontalData data) {
|
||||
const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId)
|
||||
|| (_gestureHorizontal.translation != data.translation)
|
||||
|| (_gestureHorizontal.reachRatio != data.reachRatio);
|
||||
if (changed) {
|
||||
_gestureHorizontal = data;
|
||||
const auto item = history->peer->owner().message(
|
||||
history->peer->id,
|
||||
MsgId{ data.msgBareId });
|
||||
if (item) {
|
||||
repaintItem(item);
|
||||
}
|
||||
}
|
||||
}, [=, show = _controller->uiShow()](int cursorTop) {
|
||||
auto result = HistoryView::SwipeHandlerFinishData();
|
||||
if (inSelectionMode()) {
|
||||
return result;
|
||||
}
|
||||
enumerateItems<EnumItemsDirection::BottomToTop>([&](
|
||||
not_null<Element*> view,
|
||||
int itemtop,
|
||||
int itembottom) {
|
||||
if ((cursorTop < itemtop)
|
||||
|| (cursorTop > itembottom)
|
||||
|| !view->data()->isRegular()
|
||||
|| view->data()->isService()) {
|
||||
return true;
|
||||
}
|
||||
const auto item = view->data();
|
||||
const auto canSendReply = CanSendReply(item);
|
||||
const auto canReply = (canSendReply || item->allowsForward());
|
||||
if (!canReply) {
|
||||
return true;
|
||||
}
|
||||
result.msgBareId = item->fullId().msg.bare;
|
||||
result.callback = [=, itemId = item->fullId()] {
|
||||
const auto still = show->session().data().message(itemId);
|
||||
const auto selected = selectedQuote(still);
|
||||
const auto replyToItemId = (selected.item
|
||||
? selected.item
|
||||
: still)->fullId();
|
||||
if (canSendReply) {
|
||||
_widget->replyToMessage({
|
||||
.messageId = replyToItemId,
|
||||
.quote = selected.text,
|
||||
.quoteOffset = selected.offset,
|
||||
});
|
||||
if (!selected.text.empty()) {
|
||||
_widget->clearSelected();
|
||||
}
|
||||
} else {
|
||||
HistoryView::Controls::ShowReplyToChatBox(show, {
|
||||
.messageId = replyToItemId,
|
||||
.quote = selected.text,
|
||||
.quoteOffset = selected.offset,
|
||||
});
|
||||
}
|
||||
};
|
||||
return false;
|
||||
});
|
||||
return result;
|
||||
}, _touchMaybeSelecting.value());
|
||||
}
|
||||
|
||||
bool HistoryInner::hasSelectRestriction() const {
|
||||
if (!_sharingDisallowed.current()) {
|
||||
return false;
|
||||
@@ -944,6 +1024,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
auto clip = e->rect();
|
||||
|
||||
auto context = preparePaintContext(clip);
|
||||
context.gestureHorizontal = _gestureHorizontal;
|
||||
context.highlightPathCache = &_highlightPathCache;
|
||||
_pathGradient->startFrame(
|
||||
0,
|
||||
@@ -1157,6 +1238,20 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
// paint the userpic if it intersects the painted rect
|
||||
if (userpicTop + st::msgPhotoSize > clip.top()) {
|
||||
const auto item = view->data();
|
||||
const auto hasTranslation = context.gestureHorizontal.translation
|
||||
&& (context.gestureHorizontal.msgBareId
|
||||
== item->fullId().msg.bare);
|
||||
if (hasTranslation) {
|
||||
p.translate(context.gestureHorizontal.translation, 0);
|
||||
update(
|
||||
QRect(
|
||||
st::historyPhotoLeft
|
||||
+ context.gestureHorizontal.translation,
|
||||
userpicTop,
|
||||
st::msgPhotoSize
|
||||
- context.gestureHorizontal.translation,
|
||||
st::msgPhotoSize));
|
||||
}
|
||||
if (const auto from = item->displayFrom()) {
|
||||
Dialogs::Ui::PaintUserpic(
|
||||
p,
|
||||
@@ -1192,6 +1287,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
} else {
|
||||
Unexpected("Corrupt forwarded information in message.");
|
||||
}
|
||||
if (hasTranslation) {
|
||||
p.translate(-_gestureHorizontal.translation, 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -1412,6 +1510,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
_touchScroll = _touchSelect = false;
|
||||
_horizontalScrollLocked = false;
|
||||
_touchScrollState = Ui::TouchScrollState::Manual;
|
||||
_touchMaybeSelecting = false;
|
||||
mouseActionCancel();
|
||||
return;
|
||||
}
|
||||
@@ -1434,6 +1533,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
_touchInProgress = true;
|
||||
_horizontalScrollLocked = false;
|
||||
if (_touchScrollState == Ui::TouchScrollState::Auto) {
|
||||
_touchMaybeSelecting = false;
|
||||
_touchScrollState = Ui::TouchScrollState::Acceleration;
|
||||
_touchWaitingAcceleration = true;
|
||||
_touchAccelerationTime = crl::now();
|
||||
@@ -1441,6 +1541,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
_touchStart = _touchPos;
|
||||
} else {
|
||||
_touchScroll = false;
|
||||
_touchMaybeSelecting = true;
|
||||
_touchSelectTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
_touchSelect = false;
|
||||
@@ -1454,6 +1555,7 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
mouseActionUpdate(_touchPos);
|
||||
} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
|
||||
_touchSelectTimer.cancel();
|
||||
_touchMaybeSelecting = false;
|
||||
_touchScroll = true;
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
@@ -1475,11 +1577,18 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
return;
|
||||
}
|
||||
_touchInProgress = false;
|
||||
const auto notMoved = (_touchPos - _touchStart).manhattanLength()
|
||||
< QApplication::startDragDistance();
|
||||
auto weak = Ui::MakeWeak(this);
|
||||
if (_touchSelect) {
|
||||
mouseActionFinish(_touchPos, Qt::RightButton);
|
||||
QContextMenuEvent contextMenu(QContextMenuEvent::Mouse, mapFromGlobal(_touchPos), _touchPos);
|
||||
showContextMenu(&contextMenu, true);
|
||||
if (notMoved || _touchMaybeSelecting.current()) {
|
||||
mouseActionFinish(_touchPos, Qt::RightButton);
|
||||
auto contextMenu = QContextMenuEvent(
|
||||
QContextMenuEvent::Mouse,
|
||||
mapFromGlobal(_touchPos),
|
||||
_touchPos);
|
||||
showContextMenu(&contextMenu, true);
|
||||
}
|
||||
_touchScroll = false;
|
||||
} else if (_touchScroll) {
|
||||
if (_touchScrollState == Ui::TouchScrollState::Manual) {
|
||||
@@ -1497,12 +1606,13 @@ void HistoryInner::touchEvent(QTouchEvent *e) {
|
||||
_touchWaitingAcceleration = false;
|
||||
_touchPrevPosValid = false;
|
||||
}
|
||||
} else { // One short tap is like left mouse click.
|
||||
} else if (notMoved) { // One short tap is like left mouse click.
|
||||
mouseActionStart(_touchPos, Qt::LeftButton);
|
||||
mouseActionFinish(_touchPos, Qt::LeftButton);
|
||||
}
|
||||
if (weak) {
|
||||
_touchSelectTimer.cancel();
|
||||
_touchMaybeSelecting = false;
|
||||
_touchSelect = false;
|
||||
}
|
||||
} break;
|
||||
@@ -2461,14 +2571,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
if (!item || !item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
const auto canSendReply = [&] {
|
||||
const auto peer = item->history()->peer;
|
||||
const auto topic = item->topic();
|
||||
return topic
|
||||
? Data::CanSendAnything(topic)
|
||||
: (Data::CanSendAnything(peer)
|
||||
&& (!peer->isChannel() || peer->asChannel()->amIn()));
|
||||
}();
|
||||
const auto canSendReply = CanSendReply(item);
|
||||
const auto canReply = canSendReply || item->allowsForward();
|
||||
if (canReply) {
|
||||
const auto selected = selectedQuote(item);
|
||||
@@ -3711,6 +3814,7 @@ MessageIdsList HistoryInner::getSelectedItems() const {
|
||||
|
||||
void HistoryInner::onTouchSelect() {
|
||||
_touchSelect = true;
|
||||
_touchMaybeSelecting = true;
|
||||
mouseActionStart(_touchPos, Qt::LeftButton);
|
||||
}
|
||||
|
||||
@@ -4171,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/dragging_scroll_manager.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "history/history_view_swipe_data.h"
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
|
||||
#include <QtGui/QPainterPath>
|
||||
@@ -200,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.
|
||||
@@ -419,6 +420,7 @@ private:
|
||||
void reactionChosen(const ChosenReaction &reaction);
|
||||
|
||||
void setupSharingDisallowed();
|
||||
void setupSwipeReply();
|
||||
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
||||
[[nodiscard]] bool hasCopyMediaRestriction(
|
||||
not_null<HistoryItem*> item) const;
|
||||
@@ -511,6 +513,7 @@ private:
|
||||
bool _touchSelect = false;
|
||||
bool _touchInProgress = false;
|
||||
QPoint _touchStart, _touchPrevPos, _touchPos;
|
||||
rpl::variable<bool> _touchMaybeSelecting;
|
||||
base::Timer _touchSelectTimer;
|
||||
|
||||
Ui::DraggingScrollManager _selectScroll;
|
||||
@@ -526,6 +529,8 @@ private:
|
||||
crl::time _touchTime = 0;
|
||||
base::Timer _touchScrollTimer;
|
||||
|
||||
HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal;
|
||||
|
||||
// _menu must be destroyed before _whoReactedMenuLifetime.
|
||||
rpl::lifetime _whoReactedMenuLifetime;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
@@ -1847,6 +1847,12 @@ void HistoryItem::applyEdition(
|
||||
}
|
||||
|
||||
void HistoryItem::applySentMessage(const MTPDmessage &data) {
|
||||
if (data.is_invert_media()) {
|
||||
_flags |= MessageFlag::InvertMedia;
|
||||
} else {
|
||||
_flags &= ~MessageFlag::InvertMedia;
|
||||
}
|
||||
|
||||
updateSentContent({
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(
|
||||
@@ -2520,7 +2526,7 @@ bool HistoryItem::canReact() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryItem::addPaidReaction(int count, bool anonymous) {
|
||||
void HistoryItem::addPaidReaction(int count, std::optional<bool> anonymous) {
|
||||
Expects(count >= 0);
|
||||
Expects(_history->peer->isBroadcast() || isDiscussionPost());
|
||||
|
||||
@@ -2647,9 +2653,11 @@ auto HistoryItem::topPaidReactionsWithLocal() const
|
||||
const auto i = ranges::find_if(
|
||||
result,
|
||||
[](const TopPaid &entry) { return entry.my != 0; });
|
||||
const auto peer = _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
const auto peerForMine = [&] {
|
||||
return _reactions->localPaidAnonymous()
|
||||
? nullptr
|
||||
: history()->session().user().get();
|
||||
};
|
||||
if (const auto local = _reactions->localPaidCount()) {
|
||||
const auto top = [&](int mine) {
|
||||
return ranges::count_if(result, [&](const TopPaid &entry) {
|
||||
@@ -2658,18 +2666,18 @@ auto HistoryItem::topPaidReactionsWithLocal() const
|
||||
};
|
||||
if (i != end(result)) {
|
||||
i->count += local;
|
||||
i->peer = peer;
|
||||
i->peer = peerForMine();
|
||||
i->top = top(i->count) ? 1 : 0;
|
||||
} else {
|
||||
result.push_back({
|
||||
.peer = peer,
|
||||
.peer = peerForMine(),
|
||||
.count = uint32(local),
|
||||
.top = uint32(top(local) ? 1 : 0),
|
||||
.my = uint32(1),
|
||||
});
|
||||
}
|
||||
} else if (i != end(result)) {
|
||||
i->peer = peer;
|
||||
i->peer = peerForMine();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -3454,9 +3462,10 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
||||
return _media->toPreview(options);
|
||||
} else if (!emptyText()) {
|
||||
return {
|
||||
.text = st::wrap_rtl(options.translated
|
||||
? translatedText()
|
||||
: _text)
|
||||
// wrap_rtl "adds" a newline in case text starts with quote.
|
||||
// So we remove those by DialogsPreviewText call.
|
||||
.text = st::wrap_rtl(Dialogs::Ui::DialogsPreviewText(
|
||||
options.translated ? translatedText() : _text))
|
||||
};
|
||||
}
|
||||
return {};
|
||||
@@ -5140,15 +5149,30 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
};
|
||||
|
||||
auto prepareGiveawayLaunch = [&](const MTPDmessageActionGiveawayLaunch &action) {
|
||||
const auto credits = action.vstars().value_or_empty();
|
||||
auto result = PreparedServiceText();
|
||||
result.links.push_back(fromLink());
|
||||
result.text = (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_started_group
|
||||
: tr::lng_action_giveaway_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::WithEntities);
|
||||
result.text = credits
|
||||
? (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_credits_started_group
|
||||
: tr::lng_action_giveaway_credits_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
lt_amount,
|
||||
tr::lng_action_giveaway_credits_started_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
float64(credits),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::WithEntities)
|
||||
: (_history->peer->isMegagroup()
|
||||
? tr::lng_action_giveaway_started_group
|
||||
: tr::lng_action_giveaway_started)(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -5156,15 +5180,20 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto winners = action.vwinners_count().v;
|
||||
const auto unclaimed = action.vunclaimed_count().v;
|
||||
const auto credits = action.is_stars();
|
||||
result.text = {
|
||||
(!winners
|
||||
? tr::lng_action_giveaway_results_none(tr::now)
|
||||
: unclaimed
|
||||
: (credits && unclaimed)
|
||||
? tr::lng_action_giveaway_results_credits_some(tr::now)
|
||||
: (!credits && unclaimed)
|
||||
? tr::lng_action_giveaway_results_some(tr::now)
|
||||
: tr::lng_action_giveaway_results(
|
||||
: (credits && !unclaimed)
|
||||
? tr::lng_action_giveaway_results_credits(
|
||||
tr::now,
|
||||
lt_count,
|
||||
winners))
|
||||
winners)
|
||||
: tr::lng_action_giveaway_results(tr::now, lt_count, winners))
|
||||
};
|
||||
return result;
|
||||
};
|
||||
@@ -5225,6 +5254,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
return result;
|
||||
};
|
||||
|
||||
auto prepareGiftPrize = [&](
|
||||
const MTPDmessageActionPrizeStars &action) {
|
||||
auto result = PreparedServiceText();
|
||||
_history->session().giftBoxStickersPacks().load();
|
||||
result.text = {
|
||||
(action.is_unclaimed()
|
||||
? tr::lng_prize_unclaimed_about
|
||||
: tr::lng_prize_about)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
_from->owner().peer(
|
||||
peerFromMTP(action.vboost_peer()))->name()),
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
setServiceText(action.match(
|
||||
prepareChatAddUserText,
|
||||
prepareChatJoinedByLink,
|
||||
@@ -5269,6 +5314,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
prepareBoostApply,
|
||||
preparePaymentRefunded,
|
||||
prepareGiftStars,
|
||||
prepareGiftPrize,
|
||||
PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>,
|
||||
PrepareErrorText<MTPDmessageActionEmpty>));
|
||||
|
||||
@@ -5365,8 +5411,22 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftType::Stars,
|
||||
Data::GiftType::Credits,
|
||||
data.vstars().v);
|
||||
}, [&](const MTPDmessageActionPrizeStars &data) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftCode{
|
||||
.slug = qs(data.vtransaction_id()),
|
||||
.channel = history()->owner().channel(
|
||||
peerToChannel(peerFromMTP(data.vboost_peer()))),
|
||||
.count = int(data.vstars().v),
|
||||
.giveawayMsgId = data.vgiveaway_msg_id().v,
|
||||
.type = Data::GiftType::Credits,
|
||||
.viaGiveaway = true,
|
||||
.unclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ public:
|
||||
void toggleReaction(
|
||||
const Data::ReactionId &reaction,
|
||||
HistoryReactionSource source);
|
||||
void addPaidReaction(int count, bool anonymous);
|
||||
void addPaidReaction(int count, std::optional<bool> anonymous = {});
|
||||
void cancelScheduledPaidReaction();
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidReactionSending(
|
||||
|
||||
278
Telegram/SourceFiles/history/history_view_swipe.cpp
Normal file
278
Telegram/SourceFiles/history/history_view_swipe.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
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 "history/history_view_swipe.h"
|
||||
|
||||
#include "base/platform/base_platform_haptic.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "history/history_view_swipe_data.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSwipeSlow = 0.2;
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetupSwipeHandler(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
not_null<Ui::ScrollArea*> scroll,
|
||||
Fn<void(ChatPaintGestureHorizontalData)> update,
|
||||
Fn<SwipeHandlerFinishData(int)> generateFinishByTop,
|
||||
rpl::producer<bool> dontStart) {
|
||||
constexpr auto kThresholdWidth = 50;
|
||||
constexpr auto kMaxRatio = 1.5;
|
||||
const auto threshold = style::ConvertFloatScale(kThresholdWidth);
|
||||
struct UpdateArgs {
|
||||
QPoint globalCursor;
|
||||
QPointF position;
|
||||
QPointF delta;
|
||||
bool touch = false;
|
||||
};
|
||||
struct State {
|
||||
base::unique_qptr<QObject> filter;
|
||||
Ui::Animations::Simple animationReach;
|
||||
Ui::Animations::Simple animationEnd;
|
||||
ChatPaintGestureHorizontalData data;
|
||||
SwipeHandlerFinishData finishByTopData;
|
||||
std::optional<Qt::Orientation> orientation;
|
||||
QPointF startAt;
|
||||
QPointF delta;
|
||||
int cursorTop = 0;
|
||||
bool dontStart = false;
|
||||
bool started = false;
|
||||
bool reached = false;
|
||||
bool touch = false;
|
||||
bool twoFingerScrollStarted = false;
|
||||
std::optional<UpdateArgs> pendingUpdate;
|
||||
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = widget->lifetime().make_state<State>();
|
||||
std::move(
|
||||
dontStart
|
||||
) | rpl::start_with_next([=](bool dontStart) {
|
||||
state->dontStart = dontStart;
|
||||
}, state->lifetime);
|
||||
|
||||
const auto updateRatio = [=](float64 ratio) {
|
||||
ratio = std::max(ratio, 0.);
|
||||
state->data.ratio = ratio;
|
||||
const auto overscrollRatio = std::max(ratio - 1., 0.);
|
||||
const auto translation = int(
|
||||
base::SafeRound(-std::min(ratio, 1.) * threshold)
|
||||
) + Ui::OverscrollFromAccumulated(int(
|
||||
base::SafeRound(-overscrollRatio * threshold)
|
||||
));
|
||||
state->data.msgBareId = state->finishByTopData.msgBareId;
|
||||
state->data.translation = translation;
|
||||
state->data.cursorTop = state->cursorTop;
|
||||
update(state->data);
|
||||
};
|
||||
const auto setOrientation = [=](std::optional<Qt::Orientation> o) {
|
||||
state->orientation = o;
|
||||
const auto isHorizontal = (o == Qt::Horizontal);
|
||||
scroll->viewport()->setAttribute(
|
||||
Qt::WA_AcceptTouchEvents,
|
||||
!isHorizontal);
|
||||
scroll->disableScroll(isHorizontal);
|
||||
};
|
||||
const auto processEnd = [=](std::optional<QPointF> delta = {}) {
|
||||
if (state->orientation == Qt::Horizontal) {
|
||||
const auto ratio = std::clamp(
|
||||
delta.value_or(state->delta).x() / threshold,
|
||||
0.,
|
||||
kMaxRatio);
|
||||
if ((ratio >= 1) && state->finishByTopData.callback) {
|
||||
Ui::PostponeCall(
|
||||
widget,
|
||||
state->finishByTopData.callback);
|
||||
}
|
||||
state->animationEnd.stop();
|
||||
state->animationEnd.start(
|
||||
updateRatio,
|
||||
ratio,
|
||||
0.,
|
||||
std::min(1., ratio) * st::slideWrapDuration);
|
||||
}
|
||||
setOrientation(std::nullopt);
|
||||
state->started = false;
|
||||
state->reached = false;
|
||||
};
|
||||
scroll->scrolls() | rpl::start_with_next([=] {
|
||||
if (state->orientation != Qt::Vertical) {
|
||||
processEnd();
|
||||
}
|
||||
}, state->lifetime);
|
||||
const auto animationReachCallback = [=](float64 value) {
|
||||
state->data.reachRatio = value;
|
||||
update(state->data);
|
||||
};
|
||||
const auto updateWith = [=](UpdateArgs args) {
|
||||
if (!state->started || state->touch != args.touch) {
|
||||
state->started = true;
|
||||
state->touch = args.touch;
|
||||
state->startAt = args.position;
|
||||
state->delta = QPointF();
|
||||
state->cursorTop = widget->mapFromGlobal(args.globalCursor).y();
|
||||
state->finishByTopData = generateFinishByTop(
|
||||
state->cursorTop);
|
||||
if (!state->finishByTopData.callback) {
|
||||
setOrientation(Qt::Vertical);
|
||||
}
|
||||
} else if (!state->orientation) {
|
||||
state->delta = args.delta;
|
||||
const auto diffXtoY = std::abs(args.delta.x())
|
||||
- std::abs(args.delta.y());
|
||||
constexpr auto kOrientationThreshold = 1.;
|
||||
if (diffXtoY > kOrientationThreshold) {
|
||||
if (!state->dontStart) {
|
||||
setOrientation(Qt::Horizontal);
|
||||
}
|
||||
} else if (diffXtoY < -kOrientationThreshold) {
|
||||
setOrientation(Qt::Vertical);
|
||||
} else {
|
||||
setOrientation(std::nullopt);
|
||||
}
|
||||
} else if (*state->orientation == Qt::Horizontal) {
|
||||
state->delta = args.delta;
|
||||
const auto ratio = args.delta.x() / threshold;
|
||||
updateRatio(ratio);
|
||||
constexpr auto kResetReachedOn = 0.95;
|
||||
constexpr auto kBounceDuration = crl::time(500);
|
||||
if (!state->reached && ratio >= 1.) {
|
||||
state->reached = true;
|
||||
state->animationReach.stop();
|
||||
state->animationReach.start(
|
||||
animationReachCallback,
|
||||
0.,
|
||||
1.,
|
||||
kBounceDuration);
|
||||
base::Platform::Haptic();
|
||||
} else if (state->reached
|
||||
&& ratio < kResetReachedOn) {
|
||||
state->reached = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto filter = [=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
switch (type) {
|
||||
case QEvent::Leave: {
|
||||
if (state->orientation == Qt::Horizontal) {
|
||||
processEnd();
|
||||
}
|
||||
} break;
|
||||
case QEvent::MouseMove: {
|
||||
if (state->orientation == Qt::Horizontal) {
|
||||
const auto m = static_cast<QMouseEvent*>(e.get());
|
||||
if (std::abs(m->pos().y() - state->cursorTop)
|
||||
> QApplication::startDragDistance()) {
|
||||
processEnd();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::TouchCancel: {
|
||||
const auto t = static_cast<QTouchEvent*>(e.get());
|
||||
const auto touchscreen = t->device()
|
||||
&& (t->device()->type() == base::TouchDevice::TouchScreen);
|
||||
if (!Platform::IsMac() && !touchscreen) {
|
||||
break;
|
||||
} else if (type == QEvent::TouchBegin) {
|
||||
// Reset state in case we lost some TouchEnd.
|
||||
processEnd();
|
||||
}
|
||||
const auto &touches = t->touchPoints();
|
||||
const auto released = [&](int index) {
|
||||
return (touches.size() > index)
|
||||
&& (touches.at(index).state() & Qt::TouchPointReleased);
|
||||
};
|
||||
const auto cancel = released(0)
|
||||
|| released(1)
|
||||
|| (touches.size() != (touchscreen ? 1 : 2))
|
||||
|| (type == QEvent::TouchEnd)
|
||||
|| (type == QEvent::TouchCancel);
|
||||
if (cancel) {
|
||||
processEnd(touches.empty()
|
||||
? std::optional<QPointF>()
|
||||
: (state->startAt - touches[0].pos()));
|
||||
} else {
|
||||
const auto args = UpdateArgs{
|
||||
.globalCursor = (touchscreen
|
||||
? touches[0].screenPos().toPoint()
|
||||
: QCursor::pos()),
|
||||
.position = touches[0].pos(),
|
||||
.delta = state->startAt - touches[0].pos(),
|
||||
.touch = true,
|
||||
};
|
||||
#ifdef Q_OS_MAC
|
||||
if (!state->twoFingerScrollStarted) {
|
||||
state->pendingUpdate = args;
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
updateWith(args);
|
||||
}
|
||||
return (touchscreen && state->orientation != Qt::Horizontal)
|
||||
? base::EventFilterResult::Continue
|
||||
: base::EventFilterResult::Cancel;
|
||||
} break;
|
||||
case QEvent::Wheel: {
|
||||
const auto w = static_cast<QWheelEvent*>(e.get());
|
||||
const auto phase = w->phase();
|
||||
#ifdef Q_OS_MAC
|
||||
if (phase == Qt::ScrollBegin) {
|
||||
state->twoFingerScrollStarted = true;
|
||||
if (const auto update = base::take(state->pendingUpdate)) {
|
||||
updateWith((*update));
|
||||
}
|
||||
} else if (phase == Qt::ScrollEnd
|
||||
|| phase == Qt::ScrollMomentum) {
|
||||
state->twoFingerScrollStarted = false;
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
if (Platform::IsMac() || phase == Qt::NoScrollPhase) {
|
||||
break;
|
||||
} else if (phase == Qt::ScrollBegin) {
|
||||
// Reset state in case we lost some TouchEnd.
|
||||
processEnd();
|
||||
}
|
||||
const auto cancel = w->buttons()
|
||||
|| (phase == Qt::ScrollEnd)
|
||||
|| (phase == Qt::ScrollMomentum);
|
||||
if (cancel) {
|
||||
processEnd();
|
||||
} else {
|
||||
const auto invert = (w->inverted() ? -1 : 1);
|
||||
const auto delta = Ui::ScrollDeltaF(w) * invert;
|
||||
updateWith({
|
||||
.globalCursor = w->globalPosition().toPoint(),
|
||||
.position = QPointF(),
|
||||
.delta = state->delta + delta * kSwipeSlow,
|
||||
.touch = false,
|
||||
});
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
};
|
||||
state->filter = base::make_unique_q<QObject>(
|
||||
base::install_event_filter(widget, filter));
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
31
Telegram/SourceFiles/history/history_view_swipe.h
Normal file
31
Telegram/SourceFiles/history/history_view_swipe.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
class ScrollArea;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
struct ChatPaintGestureHorizontalData;
|
||||
|
||||
struct SwipeHandlerFinishData {
|
||||
Fn<void(void)> callback;
|
||||
int64 msgBareId = 0;
|
||||
};
|
||||
|
||||
void SetupSwipeHandler(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
not_null<Ui::ScrollArea*> scroll,
|
||||
Fn<void(ChatPaintGestureHorizontalData)> update,
|
||||
Fn<SwipeHandlerFinishData(int)> generateFinishByTop,
|
||||
rpl::producer<bool> dontStart = nullptr);
|
||||
|
||||
} // namespace HistoryView
|
||||
20
Telegram/SourceFiles/history/history_view_swipe_data.h
Normal file
20
Telegram/SourceFiles/history/history_view_swipe_data.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
struct ChatPaintGestureHorizontalData {
|
||||
float64 ratio = 0.;
|
||||
float64 reachRatio = 0.;
|
||||
int64 msgBareId = 0;
|
||||
int translation = 0;
|
||||
int cursorTop = 0;
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -6469,8 +6469,11 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
||||
: nullptr;
|
||||
changed = _keyboard->updateMarkup(keyboardItem, force);
|
||||
}
|
||||
updateCmdStartShown();
|
||||
const auto controlsChanged = updateCmdStartShown();
|
||||
if (!changed) {
|
||||
if (controlsChanged) {
|
||||
updateControlsGeometry();
|
||||
}
|
||||
return;
|
||||
} else if (_keyboard->forMsgId() != wasMsgId) {
|
||||
_kbScroll->scrollTo({ 0, 0 });
|
||||
@@ -8014,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);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_thread.h"
|
||||
#include "history/view/reactions/history_view_reactions_button.h"
|
||||
#include "history/view/history_view_corner_buttons.h"
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -52,7 +53,8 @@ namespace {
|
||||
|
||||
class Item final
|
||||
: public Ui::Menu::ItemBase
|
||||
, private HistoryView::ListDelegate {
|
||||
, private ListDelegate
|
||||
, private CornerButtonsDelegate {
|
||||
public:
|
||||
Item(not_null<Ui::RpWidget*> parent, not_null<Data::Thread*> thread);
|
||||
|
||||
@@ -73,6 +75,7 @@ private:
|
||||
void setupHistory();
|
||||
void updateInnerVisibleArea();
|
||||
|
||||
// ListDelegate delegate.
|
||||
Context listContext() override;
|
||||
bool listScrollTo(int top, bool syntetic = true) override;
|
||||
void listCancelRequest() override;
|
||||
@@ -164,6 +167,16 @@ private:
|
||||
std::unique_ptr<QMimeData> data,
|
||||
Fn<void()> finished) override;
|
||||
|
||||
// CornerButtonsDelegate delegate.
|
||||
void cornerButtonsShowAtPosition(
|
||||
Data::MessagePosition position) override;
|
||||
Data::Thread *cornerButtonsThread() override;
|
||||
FullMsgId cornerButtonsCurrentId() override;
|
||||
bool cornerButtonsIgnoreVisibility() override;
|
||||
std::optional<bool> cornerButtonsDownShown() override;
|
||||
bool cornerButtonsUnreadMayBeShown() override;
|
||||
bool cornerButtonsHas(CornerButtonType type) override;
|
||||
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Data::Thread*> _thread;
|
||||
@@ -176,7 +189,8 @@ private:
|
||||
const std::unique_ptr<Ui::ElasticScroll> _scroll;
|
||||
const std::unique_ptr<Ui::FlatButton> _markRead;
|
||||
|
||||
QPointer<HistoryView::ListWidget> _inner;
|
||||
QPointer<ListWidget> _inner;
|
||||
std::unique_ptr<CornerButtons> _cornerButtons;
|
||||
rpl::event_stream<ChatPreviewAction> _actions;
|
||||
|
||||
QImage _bg;
|
||||
@@ -446,11 +460,16 @@ void Item::setupHistory() {
|
||||
this,
|
||||
_session,
|
||||
static_cast<ListDelegate*>(this)));
|
||||
_cornerButtons = std::make_unique<CornerButtons>(
|
||||
_scroll.get(),
|
||||
_chatStyle.get(),
|
||||
static_cast<CornerButtonsDelegate*>(this));
|
||||
|
||||
_markRead->shownValue() | rpl::start_with_next([=](bool shown) {
|
||||
const auto top = _top->height();
|
||||
const auto bottom = shown ? _markRead->height() : 0;
|
||||
_scroll->setGeometry(rect().marginsRemoved({ 0, top, 0, bottom }));
|
||||
_cornerButtons->updatePositions();
|
||||
}, _markRead->lifetime());
|
||||
|
||||
_scroll->scrolls(
|
||||
@@ -495,6 +514,7 @@ void Item::paintEvent(QPaintEvent *e) {
|
||||
void Item::updateInnerVisibleArea() {
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
||||
_cornerButtons->updateJumpDownVisibility();
|
||||
}
|
||||
|
||||
Context Item::listContext() {
|
||||
@@ -592,18 +612,28 @@ MessagesBarData Item::listMessagesBar(
|
||||
return {};
|
||||
}
|
||||
|
||||
auto skipped = false;
|
||||
const auto hidden = _replies && (repliesTill < 2);
|
||||
for (auto i = 0, count = int(elements.size()); i != count; ++i) {
|
||||
const auto item = elements[i]->data();
|
||||
if (!item->isRegular()
|
||||
|| item->out()
|
||||
|| (_replies && !item->replyToId())) {
|
||||
if (!item->isRegular() || (_replies && !item->replyToId())) {
|
||||
continue;
|
||||
}
|
||||
const auto inHistory = (item->history() == _history);
|
||||
if ((_replies && item->id > repliesTill)
|
||||
const auto unread = (_replies && item->id > repliesTill)
|
||||
|| (migratedTill && (inHistory || item->id > migratedTill))
|
||||
|| (historyTill && inHistory && item->id > historyTill)) {
|
||||
|| (historyTill && inHistory && item->id > historyTill);
|
||||
if (!unread) {
|
||||
skipped = true;
|
||||
}
|
||||
if (item->out()) {
|
||||
continue;
|
||||
}
|
||||
if (unread) {
|
||||
if (!skipped) {
|
||||
// Don't show jumping unread bar if scrolling up from bottom.
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
.bar = {
|
||||
.element = elements[i],
|
||||
@@ -800,6 +830,46 @@ void Item::listLaunchDrag(
|
||||
Fn<void()> finished) {
|
||||
}
|
||||
|
||||
void Item::cornerButtonsShowAtPosition(Data::MessagePosition position) {
|
||||
if (position == Data::UnreadMessagePosition) {
|
||||
position = Data::MaxMessagePosition;
|
||||
}
|
||||
_inner->showAtPosition(
|
||||
position,
|
||||
{},
|
||||
_cornerButtons->doneJumpFrom(position.fullId, {}, true));
|
||||
}
|
||||
|
||||
Data::Thread *Item::cornerButtonsThread() {
|
||||
return _thread;
|
||||
}
|
||||
|
||||
FullMsgId Item::cornerButtonsCurrentId() {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsIgnoreVisibility() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<bool> Item::cornerButtonsDownShown() {
|
||||
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
|
||||
if (top < _scroll->scrollTopMax()) {
|
||||
return true;
|
||||
} else if (_inner->loadedAtBottomKnown()) {
|
||||
return !_inner->loadedAtBottom();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsUnreadMayBeShown() {
|
||||
return _inner->loadedAtBottomKnown();
|
||||
}
|
||||
|
||||
bool Item::cornerButtonsHas(CornerButtonType type) {
|
||||
return (type == CornerButtonType::Down);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatPreview MakeChatPreview(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user