Compare commits

..

136 Commits

Author SHA1 Message Date
John Preston
f05191e668 Version 5.5.2: Fix build. 2024-09-09 21:30:15 +04:00
John Preston
1ba189e59d Version 5.5.2.
- Support two bottom buttons in web apps.
- Fix text layout outside of the message bubble glitch.
- Don't stop video when dragging media viewer in window mode.
2024-09-09 21:12:33 +04:00
23rd
c40ca70aa6 Added date of admin promotional to EditAdminBox. 2024-09-09 17:20:52 +03:00
23rd
521f991167 Replaced float labels with text in dividers in EditAdminBox. 2024-09-09 17:20:52 +03:00
23rd
edf1417bbb Added date of restriction to EditRestrictedBox. 2024-09-09 17:20:52 +03:00
23rd
686e9643ad Replaced channel participant parsing with modern way in admin log. 2024-09-09 17:20:52 +03:00
23rd
975460d268 Added api support for dates of chat participant data. 2024-09-09 17:20:52 +03:00
23rd
6b0c606d25 Switched type of subscription date from Qt to TimeId. 2024-09-09 17:20:52 +03:00
23rd
93ff0bdcff Slightly improved style of box for sending GIF with caption. 2024-09-09 17:20:52 +03:00
23rd
bf9d90ca4e Fixed emoji display in header of link for dialog filters. 2024-09-09 17:20:52 +03:00
23rd
d6e5e1e8f7 Added handle of flood errors in request of join to chatlist. 2024-09-09 17:20:52 +03:00
23rd
d3ae2ef9ea Synced local English phrases with server. 2024-09-09 17:20:52 +03:00
John Preston
40b0854704 Don't pause video on viewer drag. 2024-09-09 17:54:16 +04:00
John Preston
9aec6b6496 Set min width for field collapsed quotes. 2024-09-09 17:24:15 +04:00
John Preston
d01f977960 Don't offer hello-sticker to user you've blocked. 2024-09-09 16:42:02 +04:00
John Preston
1444009ee2 Update submodules. 2024-09-09 14:58:40 +04:00
John Preston
14ee9bee26 Fix quotes starting with custom urls. 2024-09-09 14:58:40 +04:00
Ilya Fedin
93587ddc3c Allow scale preview in separate window on Wayland 2024-09-09 14:49:44 +04:00
John Preston
0c4d03477e Fix text layout in some cases. 2024-09-09 14:28:37 +04:00
John Preston
a35092f012 Support second button in web apps. 2024-09-09 13:29:00 +04:00
John Preston
49ee7ee52b Version 5.5.1: Update cmake_helpers. 2024-09-07 00:25:47 +04:00
John Preston
52c77a1970 Version 5.5.1.
- Fix crash in peer short info box.
2024-09-06 23:30:32 +04:00
John Preston
e4269ae7fb Fix crash in PeerShortInfoBox. 2024-09-06 23:28:43 +04:00
John Preston
bd6011c524 Version 5.5.
- Star Giveaways.
- Swipe-to-Reply.
- Audio device selection in one-on-one calls.
- Text selection in collapsed / expanded quotes.
2024-09-06 21:57:47 +04:00
23rd
46cb3ec103 Added box for boosts with credits. 2024-09-06 21:57:47 +04:00
23rd
0241129948 Added support of admin log filter for extended subscriptions. 2024-09-06 21:57:47 +04:00
23rd
e44aca06cb Added support of admin log event of extended subscriptions. 2024-09-06 21:57:47 +04:00
23rd
19d386f977 Added support of credits to giveaway info results. 2024-09-06 21:57:47 +04:00
John Preston
a67fdda913 Fix the Release build for macOS. 2024-09-06 21:57:47 +04:00
John Preston
5286c7b1c3 Allow selecting text in collapsed quotes. 2024-09-06 21:57:47 +04:00
23rd
d9d96d0a6f Fixed display of custom badge in selected messages for giveaways. 2024-09-06 21:57:47 +04:00
23rd
13353bb615 Improved phrases in service message for giveaway results with credits. 2024-09-06 21:57:47 +04:00
23rd
d78348fd16 Fixed crash in box for giveaway create when winners number is constant. 2024-09-06 21:57:46 +04:00
23rd
1e8e660133 Improved phrases in top bar from box for giveaway creation with credits. 2024-09-06 21:57:46 +04:00
23rd
5196982c98 Improved phrases in box for information of giveaway with credits. 2024-09-06 21:57:46 +04:00
23rd
99f857fbf6 Improved phrases in service message for started giveaway with credits. 2024-09-06 21:57:46 +04:00
23rd
0982aa166a Added support of prepaid giveaway with credits. 2024-09-06 21:57:46 +04:00
23rd
3ae9f86097 Slightly improved display of credits in boosts from statistics list. 2024-09-06 21:57:46 +04:00
23rd
9efd9b0d68 Slightly improved timestamp parsing in Api::Boosts. 2024-09-06 21:57:46 +04:00
John Preston
267a73f355 Update API scheme on layer 187. 2024-09-06 21:57:46 +04:00
John Preston
437d8ea890 Fix build with MSVC. 2024-09-06 21:57:46 +04:00
23rd
7ed92ec402 Added credits icon to boosts in statistics list. 2024-09-06 21:57:46 +04:00
23rd
678d9ffbf9 Added support of giveaway prize action messages to export to JSON. 2024-09-06 21:57:46 +04:00
23rd
bc4b427ed1 Added support of giveaway prize action messages to export to HTML. 2024-09-06 21:57:46 +04:00
23rd
36141a9df9 Added support of giveaway results messages to export to JSON. 2024-09-06 21:57:46 +04:00
23rd
d68ba75457 Improved process of giveaway messages for export to JSON. 2024-09-06 21:57:46 +04:00
23rd
00ad32c5f4 Added support of giveaway results messages to export to HTML. 2024-09-06 21:57:46 +04:00
23rd
064bab60ff Improved process of giveaway messages for export to HTML. 2024-09-06 21:57:46 +04:00
23rd
5b146217c0 Added support for credits in messages of prize in giveaways. 2024-09-06 21:57:46 +04:00
23rd
202c81b2e5 Added support for credits in messages for starting and ending giveaways. 2024-09-06 21:57:46 +04:00
23rd
4520480604 Added support of credits giveaway to ReceiptCreditsBox. 2024-09-06 21:57:46 +04:00
23rd
aea87bb5cb United buttons for premium giveaway and premium gifts to single button. 2024-09-06 21:57:46 +04:00
23rd
19492f7e7b Improved prize description for credits in box for giveaway creation. 2024-09-06 21:57:46 +04:00
23rd
51030e3c45 Added list of options to box for giveaway creation with credits. 2024-09-06 21:57:46 +04:00
23rd
e6b8b4be18 Added initial payment support for credits giveaway. 2024-09-06 21:36:03 +04:00
23rd
6ac13b7f80 Added button for credits giveaway to box for giveaway creation. 2024-09-06 21:36:03 +04:00
23rd
7e5e6003a9 Added initial api support for credits giveaway options. 2024-09-06 21:36:03 +04:00
23rd
f445440995 Added initial data structure for credits giveaway options. 2024-09-06 21:36:03 +04:00
John Preston
d81547f091 Support cloud-saved paid reaction privacy. 2024-09-06 21:36:03 +04:00
John Preston
2b185d491b Update API scheme to layer 187. 2024-09-06 21:36:03 +04:00
John Preston
ca47440950 Don't treat .gif as "small image". 2024-09-06 21:07:17 +04:00
John Preston
5e32602f4a Add elastic overscroll to swipe-to-reply. 2024-09-06 21:07:02 +04:00
23rd
1f4516028c Fixed desync of repaint of userpics and messages while swipe-on-reply. 2024-09-06 19:52:29 +03:00
23rd
7f29d269a3 Changed swipe-on-reply detection only when scroll has began. 2024-09-06 17:11:16 +03:00
23rd
62c3374911 Disabled swipe-to-reply for channels. 2024-09-06 12:04:42 +03:00
23rd
2116e04af5 Allowed to swipe-to-reply only with started initial of touches. 2024-09-06 11:56:21 +03:00
John Preston
a97d5e80c7 Fix custom reaction as a quick one. 2024-09-05 19:13:52 +04:00
John Preston
b5098038d0 Add two-year ttl time for account. 2024-09-05 17:05:09 +04:00
John Preston
6fce718252 Add jump-to-end button to chat preview. 2024-09-05 14:50:38 +04:00
John Preston
40cf96202d Add 1.5 years account ttl value. 2024-09-05 14:11:41 +04:00
John Preston
c18e59218e Show internal list of apps as Stars Examples. 2024-09-05 13:14:07 +04:00
John Preston
216865a20d Fix file albums with inline keyboard buttons. 2024-09-05 12:01:43 +04:00
John Preston
6c5036ee8d Fix typo in swipe-to-reply through QWheelEvent. 2024-09-05 11:52:25 +04:00
Viliansh
dc2f59ca24 Added org.telegram as developer_id in the flatpak metainfo.xml
Co-authored-by: ilya-fedin <fedin-ilja2010@ya.ru>
2024-09-05 09:47:43 +02:00
Viliansh
6f6457137e removed update_contact from flatpak metainfo.xml 2024-09-05 09:47:43 +02:00
Viliansh
bc5cb6e2a2 Changed dev name in org.telegram.desktop.metainfo.xml
In flathub, and it's front-ends like KDE's Discover or GNOME Software, Telegram shows "By John Preston", and some users were hesitant to install it afraid that was "fake", so i changed it to Telegram, this would also make it consistent with snap, thanks!
2024-09-05 09:47:43 +02:00
John Preston
16825fff41 Use QWheelEvent::inverted() in swipe gesture. 2024-09-05 11:30:37 +04:00
John Preston
5024f1db8c Beta version 5.4.6.
- Add Swipe-To-Reply for modern touchpads and touchscreens.
- Add text selection in collapsed and expanded blockquotes.
- Fix text rendering in 100% interface scale on Windows.
2024-09-04 18:39:16 +04:00
23rd
a60385fc3d Removed QGraphicsOpacityEffect usage from media view overlay widget. 2024-09-04 18:39:16 +04:00
23rd
b20e2c37c1 Fixed build on Qt6. 2024-09-04 18:39:16 +04:00
John Preston
acd40cbeb6 Apply invert_media flag on message send finish. 2024-09-04 18:32:48 +04:00
23rd
6a45a862dd Removed wheel events from scale slider in section of main settings. 2024-09-04 16:01:55 +02:00
23rd
5bd45e9a20 Fixed updating of left userpic with offset from swipe-on-reply. 2024-09-04 16:01:55 +02:00
23rd
52e42f23ab Improved animation speed on release of swipe-on-reply with various x. 2024-09-04 16:01:54 +02:00
23rd
96b7755cde Fixed updating of message on bounce animation of swipe-on-reply icon. 2024-09-04 16:01:54 +02:00
23rd
c589ee1ca5 Fixed overlap of reply icon on left swipe of message with right action. 2024-09-04 16:01:54 +02:00
23rd
18c9ee093b Fixed unwanted top offset of dialogs widget when float player opened. 2024-09-04 16:01:54 +02:00
John Preston
b82fa3112c Improve touchscreen swipt-to-reply. 2024-09-04 16:01:20 +02:00
John Preston
8d0f66d562 Fix touchscreen history scrolling. 2024-09-04 14:06:11 +02:00
John Preston
20a5e0ba73 Fix controls layout after adding a bot to a group.
Fixes https://bugs.telegram.org/c/19451
2024-09-04 14:56:50 +04:00
John Preston
5a2667c71e Fix reply previews to blockquotes / codeblocks. 2024-09-04 14:45:35 +04:00
John Preston
1f4a8d7eb6 Fix build with MSVC. 2024-09-04 14:45:07 +04:00
John Preston
4430bd0328 Improve lottie icon layout in group calls. 2024-09-04 14:14:12 +04:00
John Preston
ab2e7f4c03 Fix media viewer create on non-primary screen. 2024-09-04 14:14:12 +04:00
John Preston
54e5c06b4d Revert updated Harfbuzz-NG in Qt 5.15.15.
This fixes the regression with text rendering in 100% scale.

Fixes #28340.
2024-09-04 14:14:12 +04:00
23rd
add5a6a0be Added debug util to ensure end of life of objects. 2024-09-04 14:14:12 +04:00
23rd
89c2ba4293 Weakened gesture orientation check within swipe-to-reply. 2024-09-04 14:14:12 +04:00
23rd
dd100fb709 Fixed back gesture responsiveness for swipe-to-reply on macOS. 2024-09-04 14:14:12 +04:00
John Preston
e8dd2b9e7b Use only integer translations in reply swipe. 2024-09-04 14:14:12 +04:00
23rd
71e3cd227c Added initial swipe-to-reply to section for replies. 2024-09-04 14:14:12 +04:00
23rd
1648c31a22 Moved out swipe-to-reply setup to inner method in HistoryInner. 2024-09-04 14:14:12 +04:00
23rd
f8c820f319 Fixed swipe-to-reply on macOS. 2024-09-04 14:14:12 +04:00
John Preston
300f35e78f Implement both touch and wheel swipe-to-reply. 2024-09-04 14:14:12 +04:00
23rd
2aa5849997 Improved horizontal animation for reply icon on left swipe. 2024-09-04 14:14:12 +04:00
23rd
fb1b845211 Improved process of message repaint on swipe to reply. 2024-09-04 14:14:11 +04:00
23rd
3d2af9db8e Improved distance of reply icon on left swipe. 2024-09-04 14:14:11 +04:00
23rd
6129e5a1cf Added bounce animation on finish of replying with left swipe. 2024-09-04 14:14:11 +04:00
23rd
7f70ee1227 Added initial ability to reply with left swipe. 2024-09-04 14:14:11 +04:00
23rd
c9f7da6e82 Added ability to paint icons in center of QRectF. 2024-09-04 10:33:28 +03:00
23rd
f956c0f227 Added experimental option to disable touch bar on macOS. 2024-09-04 10:12:08 +03:00
23rd
df935e0477 Improved code style of gifMaxStatusWidth static function. 2024-09-04 10:11:57 +03:00
23rd
841f1afe1e Improved subtitle space in permissions and restrictions edit boxes. 2024-09-04 10:11:54 +03:00
23rd
fb8b88557e Added ability to kick participants from section of admin log. 2024-09-04 10:11:49 +03:00
23rd
2b502b22b9 Replaced PeerData::generateUserpicImage with static function. 2024-09-04 10:11:44 +03:00
23rd
5ac80d2655 Fixed text clipping at bottom in settings section for cloud password. 2024-09-04 10:11:41 +03:00
23rd
8aa7499e63 Fixed width of stats PointDetailsWidget for charts with currency. 2024-09-04 10:11:36 +03:00
23rd
8cd5e51982 Added missed handler of server error when try to reset cloud password. 2024-09-04 10:11:31 +03:00
23rd
e5c2133446 Disabled Qt build on Windows without successful applied patches. 2024-09-04 10:11:17 +03:00
John Preston
3dccdf2f05 Beta version 5.4.5.
- Fix possible crash in text rendering.
2024-09-01 10:19:39 +04:00
John Preston
8a708c6655 Update Qt to 5.15.15 on Windows. 2024-09-01 10:19:39 +04:00
Ilya Fedin
9e1d9eee4b Fix snap build 2024-08-31 20:09:36 +02:00
Ilya Fedin
f30aabc365 Update Qt 6.7.2 -> 6.8.0 2024-08-31 19:54:58 +02:00
John Preston
a5546d016f Fix possible crash in long-word texts. 2024-08-31 21:48:52 +04:00
John Preston
f79d70d112 Use Ui::Text::String in Ui::RoundButton. 2024-08-31 21:48:52 +04:00
John Preston
ec28f258fb Use full bubble width in message text rendering.
Fixes #28331.
2024-08-31 21:48:52 +04:00
John Preston
08f3a6fb40 Fix local group sent-as display. 2024-08-31 21:48:52 +04:00
John Preston
8823d5256f Fix indentation. 2024-08-31 21:48:52 +04:00
23rd
60e7aa90d2 Added ability to report profile photo from peer info section. 2024-08-31 12:34:50 +03:00
John Preston
71357a9546 Fix archive chats list bidi support. 2024-08-29 17:48:06 +04:00
John Preston
a5b06e9c56 Fix possible crash in text elision. 2024-08-29 17:41:15 +04:00
23rd
0f94419f6d Improved fix of crash in video messages playback.
Regression was introduced in ad3e447f08.
2024-08-29 17:37:55 +04:00
John Preston
94e7aabea5 Fix spaces after emoji in text rendering. 2024-08-29 17:19:28 +04:00
John Preston
f6c816cafe Beta version 5.4.4.
- Fix wrong layout and crashes in text shaping.
- Fix crashes in voice / video messages playback.
2024-08-29 13:01:06 +04:00
John Preston
4cf160e8dc Fix UB in text shaping. 2024-08-29 12:54:52 +04:00
John Preston
9252be5e8c Fix crash in video messages playback.
Regression was introduced in ad3e447f08.
2024-08-29 12:13:51 +04:00
John Preston
ed342eea64 Beta version 5.4.3. (Windows only)
- Fix working on Windows 7.
2024-08-29 10:32:53 +04:00
184 changed files with 5164 additions and 1758 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,4,2,0
PRODUCTVERSION 5,4,2,0
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,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.2.0"
VALUE "FileVersion", "5.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.4.2.0"
VALUE "ProductVersion", "5.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,4,2,0
PRODUCTVERSION 5,4,2,0
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,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.2.0"
VALUE "FileVersion", "5.5.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.4.2.0"
VALUE "ProductVersion", "5.5.2.0"
END
END
BLOCK "VarFileInfo"

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

@@ -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(

View File

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

View File

@@ -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,

View File

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

View File

@@ -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();

View File

@@ -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{

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<>>(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

@@ -734,7 +734,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);

View File

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

View File

@@ -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)

View File

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

View File

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

View File

@@ -162,7 +162,6 @@ public:
private:
void prepare() override;
void prepareRows();
RectParts customCornersFilling() override;
void resizeEvent(QResizeEvent *e) override;

View File

@@ -79,7 +79,8 @@ void ProcessUserpic(
if (!state->userpicView.cloud) {
GenerateImage(
state,
peer->generateUserpicImage(
PeerData::GenerateUserpicImage(
peer,
state->userpicView,
st::shortInfoWidth * style::DevicePixelRatio(),
0),

View File

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

View File

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

View File

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

View File

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

View File

@@ -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),

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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()

View File

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

View File

@@ -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 = [&] {

View File

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

View File

@@ -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(),
};

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(

View File

@@ -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");

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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({

View File

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

View File

@@ -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(

View File

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

View File

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

View File

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

View File

@@ -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 &) {
});
}

View File

@@ -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(

View 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

View 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

View 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

View File

@@ -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();

View File

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

View File

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

View File

@@ -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(

View File

@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_corner_buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/chat/chat_style.h"
#include "ui/controls/jump_down_button.h"
#include "ui/widgets/elastic_scroll.h"
#include "ui/widgets/scroll_area.h"
#include "base/qt/qt_key_modifiers.h"
#include "history/history.h"
#include "history/history_item.h"
@@ -33,17 +34,41 @@ CornerButtons::CornerButtons(
not_null<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate)
: _scroll(parent)
: CornerButtons(
parent,
[=](QEvent *e) { return parent->viewportEvent(e); },
st,
delegate) {
}
CornerButtons::CornerButtons(
not_null<Ui::ElasticScroll*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate)
: CornerButtons(
parent,
[=](QEvent *e) { return parent->viewportEvent(e); },
st,
delegate) {
}
CornerButtons::CornerButtons(
not_null<QWidget*> parent,
Fn<bool(QEvent*)> scrollViewportEvent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate)
: _parent(parent)
, _scrollViewportEvent(std::move(scrollViewportEvent))
, _delegate(delegate)
, _down(
parent,
st->value(parent->lifetime(), st::historyToDown))
st->value(_stLifetime, st::historyToDown))
, _mentions(
parent,
st->value(parent->lifetime(), st::historyUnreadMentions))
st->value(_stLifetime, st::historyUnreadMentions))
, _reactions(
parent,
st->value(parent->lifetime(), st::historyUnreadReactions)) {
st->value(_stLifetime, st::historyUnreadReactions)) {
_down.widget->addClickHandler([=] { downClick(); });
_mentions.widget->addClickHandler([=] { mentionsClick(); });
_reactions.widget->addClickHandler([=] { reactionsClick(); });
@@ -68,7 +93,7 @@ bool CornerButtons::eventFilter(QObject *o, QEvent *e) {
&& (o == _down.widget
|| o == _mentions.widget
|| o == _reactions.widget)) {
return _scroll->viewportEvent(e);
return _scrollViewportEvent(e);
}
return QObject::eventFilter(o, e);
}
@@ -200,9 +225,7 @@ void CornerButtons::showAt(MsgId id) {
}
}
void CornerButtons::updateVisibility(
CornerButtonType type,
bool shown) {
void CornerButtons::updateVisibility(Type type, bool shown) {
auto &button = buttonByType(type);
if (button.shown != shown) {
button.shown = shown;
@@ -291,7 +314,7 @@ void CornerButtons::updatePositions() {
historyDownShown);
_down.widget->moveToRight(
st::historyToDownPosition.x(),
_scroll->height() - top);
_parent->height() - top);
}
{
const auto right = anim::interpolate(
@@ -302,7 +325,7 @@ void CornerButtons::updatePositions() {
0,
_down.widget->height() + skip,
historyDownShown);
const auto top = _scroll->height()
const auto top = _parent->height()
- _mentions.widget->height()
- st::historyToDownPosition.y()
- shift;
@@ -321,7 +344,7 @@ void CornerButtons::updatePositions() {
0,
_mentions.widget->height() + skip,
unreadMentionsShown);
const auto top = _scroll->height()
const auto top = _parent->height()
- _reactions.widget->height()
- st::historyToDownPosition.y()
- shift;
@@ -355,7 +378,7 @@ Fn<void(bool found)> CornerButtons::doneJumpFrom(
}
if (!found && !ignoreMessageNotFound) {
Ui::Toast::Show(
_scroll.get(),
_parent.get(),
tr::lng_message_not_found(tr::now));
}
};

View File

@@ -17,6 +17,7 @@ struct FullMsgId;
namespace Ui {
class ChatStyle;
class ScrollArea;
class ElasticScroll;
class JumpDownButton;
} // namespace Ui
@@ -61,6 +62,10 @@ public:
not_null<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate);
CornerButtons(
not_null<Ui::ElasticScroll*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate);
using Type = CornerButtonType;
@@ -91,6 +96,12 @@ public:
bool ignoreMessageNotFound = false);
private:
CornerButtons(
not_null<QWidget*> parent,
Fn<bool(QEvent*)> scrollViewportEvent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate);
bool eventFilter(QObject *o, QEvent *e) override;
void computeCurrentReplyReturn();
@@ -99,9 +110,12 @@ private:
[[nodiscard]] History *lookupHistory() const;
void showAt(MsgId id);
const not_null<Ui::ScrollArea*> _scroll;
const not_null<QWidget*> _parent;
const Fn<bool(QEvent*)> _scrollViewportEvent;
const not_null<CornerButtonsDelegate*> _delegate;
rpl::lifetime _stLifetime;
CornerButton _down;
CornerButton _mentions;
CornerButton _reactions;

View File

@@ -315,6 +315,10 @@ void UnreadBar::paint(
int y,
int w,
bool chatWide) const {
const auto previousTranslation = p.transform().dx();
if (previousTranslation != 0) {
p.translate(-previousTranslation, 0);
}
const auto st = context.st;
const auto bottom = y + height();
y += marginTop();
@@ -350,6 +354,9 @@ void UnreadBar::paint(
(w - width) / 2,
y + (skip / 2) + st::historyUnreadBarFont->ascent,
text);
if (previousTranslation != 0) {
p.translate(previousTranslation, 0);
}
}
void DateBadge::init(const QString &date) {

View File

@@ -142,7 +142,8 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallBarContentByCall(
state->someUserpicsNotLoaded = false;
for (auto &userpic : state->userpics) {
userpic.peer->loadUserpic();
auto image = userpic.peer->generateUserpicImage(
auto image = PeerData::GenerateUserpicImage(
userpic.peer,
userpic.view,
userpicSize * style::DevicePixelRatio());
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);

View File

@@ -2305,6 +2305,20 @@ void ListWidget::paintUserpics(
// 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()) {
from->paintUserpicLeft(
p,
@@ -2337,6 +2351,9 @@ void ListWidget::paintUserpics(
} else {
Unexpected("Corrupt forwarded information in message.");
}
if (hasTranslation) {
p.translate(-context.gestureHorizontal.translation, 0);
}
}
return true;
});
@@ -2932,6 +2949,7 @@ void ListWidget::touchEvent(QTouchEvent *e) {
_touchSelectTimer.cancel();
_touchScroll = _touchSelect = false;
_touchScrollState = Ui::TouchScrollState::Manual;
_touchMaybeSelecting = false;
mouseActionCancel();
return;
}
@@ -2952,6 +2970,7 @@ void ListWidget::touchEvent(QTouchEvent *e) {
_touchInProgress = true;
if (_touchScrollState == Ui::TouchScrollState::Auto) {
_touchMaybeSelecting = false;
_touchScrollState = Ui::TouchScrollState::Acceleration;
_touchWaitingAcceleration = true;
_touchAccelerationTime = crl::now();
@@ -2959,6 +2978,7 @@ void ListWidget::touchEvent(QTouchEvent *e) {
_touchStart = _touchPos;
} else {
_touchScroll = false;
_touchMaybeSelecting = true;
_touchSelectTimer.callOnce(QApplication::startDragTime());
}
_touchSelect = false;
@@ -2971,6 +2991,7 @@ void ListWidget::touchEvent(QTouchEvent *e) {
mouseActionUpdate(_touchPos);
} else if (!_touchScroll && (_touchPos - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
_touchSelectTimer.cancel();
_touchMaybeSelecting = false;
_touchScroll = true;
touchUpdateSpeed();
}
@@ -2988,13 +3009,22 @@ void ListWidget::touchEvent(QTouchEvent *e) {
} break;
case QEvent::TouchEnd: {
if (!_touchInProgress) return;
if (!_touchInProgress) {
return;
}
_touchInProgress = false;
auto weak = Ui::MakeWeak(this);
const auto notMoved = (_touchPos - _touchStart).manhattanLength()
< QApplication::startDragDistance();
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) {
@@ -3011,12 +3041,13 @@ void ListWidget::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;
@@ -3056,6 +3087,10 @@ void ListWidget::touchScrollUpdated(const QPoint &screenPos) {
touchUpdateSpeed();
}
rpl::producer<bool> ListWidget::touchMaybeSelectingValue() const {
return _touchMaybeSelecting.value();
}
void ListWidget::enterEventHook(QEnterEvent *e) {
mouseActionUpdate(QCursor::pos());
return TWidget::enterEventHook(e);
@@ -3112,6 +3147,7 @@ void ListWidget::updateDragSelection() {
void ListWidget::onTouchSelect() {
_touchSelect = true;
_touchMaybeSelecting = true;
mouseActionStart(_touchPos, Qt::LeftButton);
}

View File

@@ -327,6 +327,7 @@ public:
void selectItemAsGroup(not_null<HistoryItem*> item);
void touchScrollUpdated(const QPoint &screenPos);
[[nodiscard]] rpl::producer<bool> touchMaybeSelectingValue() const;
[[nodiscard]] bool loadedAtTopKnown() const;
[[nodiscard]] bool loadedAtTop() const;
@@ -830,6 +831,7 @@ private:
bool _touchSelect = false;
bool _touchInProgress = false;
QPoint _touchStart, _touchPrevPos, _touchPos;
rpl::variable<bool> _touchMaybeSelecting;
base::Timer _touchSelectTimer;
Ui::DraggingScrollManager _selectScroll;

View File

@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/round_rect.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_extended_data.h"
#include "ui/power_saving.h"
#include "data/components/factchecks.h"
#include "data/components/sponsored_messages.h"
@@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h" // IsNightMode.
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "styles/style_chat.h"
@@ -1091,6 +1093,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
const auto item = data();
const auto media = this->media();
const auto hasGesture = context.gestureHorizontal.translation
&& (context.gestureHorizontal.msgBareId == item->fullId().msg.bare);
if (hasGesture) {
p.translate(context.gestureHorizontal.translation, 0);
}
if (item->hasUnrequestedFactcheck()) {
item->history()->session().factchecks().requestFor(item);
}
@@ -1135,6 +1143,15 @@ void Message::draw(Painter &p, const PaintContext &context) const {
const auto displayInfo = needInfoDisplay();
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
const auto keyboard = item->inlineReplyKeyboard();
const auto fullGeometry = g;
if (keyboard) {
// We need to count geometry without keyboard for bubble selection
// intervals counting below.
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
g.setHeight(g.height() - keyboardHeight);
}
auto mediaSelectionIntervals = (!context.selected() && mediaDisplayed)
? media->getBubbleSelectionIntervals(context.selection)
: std::vector<Ui::BubbleSelectionInterval>();
@@ -1169,25 +1186,22 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (customHighlight) {
media->drawHighlight(p, context, localMediaTop);
} else {
paintHighlight(p, context, g.height());
paintHighlight(p, context, fullGeometry.height());
}
const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
if (roll) {
p.save();
p.translate(g.center());
p.translate(fullGeometry.center());
p.rotate(roll.rotate);
p.scale(roll.scale, roll.scale);
p.translate(-g.center());
p.translate(-fullGeometry.center());
}
p.setTextPalette(stm->textPalette);
const auto keyboard = item->inlineReplyKeyboard();
const auto messageRounding = countMessageRounding();
if (keyboard) {
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
g.setHeight(g.height() - keyboardHeight);
const auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
p.translate(keyboardPosition);
keyboard->paint(
@@ -1481,6 +1495,79 @@ void Message::draw(Painter &p, const PaintContext &context) const {
}
}
}
if (hasGesture) {
p.translate(-context.gestureHorizontal.translation, 0);
constexpr auto kShiftRatio = 1.5;
constexpr auto kBouncePart = 0.25;
constexpr auto kMaxHeightRatio = 3.5;
constexpr auto kStrokeWidth = 2.;
constexpr auto kWaveWidth = 10.;
const auto isLeftSize = (!context.outbg)
|| delegate()->elementIsChatWide();
const auto ratio = std::min(context.gestureHorizontal.ratio, 1.);
const auto reachRatio = context.gestureHorizontal.reachRatio;
const auto size = st::historyFastShareSize;
const auto outerWidth = st::historySwipeIconSkip
+ (isLeftSize ? rect::right(g) : width())
+ ((g.height() < size * kMaxHeightRatio)
? rightActionSize().value_or(QSize()).width()
: 0);
const auto shift = std::min(
(size * kShiftRatio * context.gestureHorizontal.ratio),
-1. * context.gestureHorizontal.translation
) + (st::historySwipeIconSkip * ratio * (isLeftSize ? .7 : 1.));
const auto rect = QRectF(
outerWidth - shift,
g.y() + (g.height() - size) / 2,
size,
size);
const auto center = rect::center(rect);
const auto spanAngle = ratio * arc::kFullLength;
const auto strokeWidth = style::ConvertFloatScale(kStrokeWidth);
const auto reachScale = std::clamp(
(reachRatio > kBouncePart)
? (kBouncePart * 2 - reachRatio)
: reachRatio,
0.,
1.);
auto pen = Window::Theme::IsNightMode()
? QPen(anim::with_alpha(context.st->msgServiceFg()->c, 0.3))
: QPen(context.st->msgServiceBg());
pen.setWidthF(strokeWidth - (1. * (reachScale / kBouncePart)));
const auto arcRect = rect - Margins(strokeWidth);
p.save();
{
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.setOpacity(ratio);
p.translate(center);
if (reachScale) {
p.scale(-(1. + 1. * reachScale), (1. + 1. * reachScale));
} else {
p.scale(-1., 1.);
}
p.translate(-center);
// All the next draws are mirrored.
p.drawEllipse(rect);
context.st->historyFastShareIcon().paintInCenter(p, rect);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
p.drawArc(arcRect, arc::kQuarterLength, spanAngle);
// p.drawArc(arcRect, arc::kQuarterLength, spanAngle);
if (reachRatio) {
const auto w = style::ConvertFloatScale(kWaveWidth);
p.setOpacity(ratio - reachRatio);
p.drawArc(
arcRect + Margins(reachRatio * reachRatio * w),
arc::kQuarterLength,
spanAngle);
}
}
p.restore();
}
}
void Message::paintCommentsButton(
@@ -1933,6 +2020,7 @@ void Message::paintText(
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
.useFullWidth = true,
});
}
@@ -3290,7 +3378,7 @@ void Message::refreshReactions() {
item,
weak.get(),
1,
Payments::LookupMyPaidAnonymous(item),
std::nullopt,
controller->uiShow());
return;
} else {
@@ -3489,6 +3577,9 @@ bool Message::allowTextSelectionByHandler(
return true;
}
}
if (dynamic_cast<Ui::Text::BlockquoteClickHandler*>(handler.get())) {
return true;
}
return false;
}
@@ -3830,10 +3921,10 @@ void Message::drawRightAction(
} else if (_rightAction->second) {
st->historyFastCloseIcon().paintInCenter(
p,
{ left, top, size->width(), size->width() });
QRect(left, top, size->width(), size->width()));
st->historyFastMoreIcon().paintInCenter(
p,
{ left, size->width() + top, size->width(), size->width() });
QRect(left, size->width() + top, size->width(), size->width()));
} else {
const auto &icon = data()->isSponsored()
? st->historyFastCloseIcon()
@@ -3842,7 +3933,7 @@ void Message::drawRightAction(
&& this->context() != Context::SavedSublist)
? st->historyFastShareIcon()
: st->historyGoToOriginalIcon();
icon.paintInCenter(p, { left, top, size->width(), size->height() });
icon.paintInCenter(p, Rect(left, top, *size));
}
}

View File

@@ -91,7 +91,7 @@ constexpr auto kPremiumToastDuration = 5 * crl::time(1000);
result->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(result);
const auto font = st::historyPremiumViewSet.font;
const auto font = st::historyPremiumViewSet.style.font;
const auto top = (result->height() - font->height) / 2;
auto pen = st::historyPremiumViewSet.textFg->p;
p.setPen(pen);
@@ -229,7 +229,7 @@ void PaidReactionToast::showFor(
child->show();
const auto leftSkip = skip + size + skip - st.padding.left();
const auto undoFont = st::historyPremiumViewSet.font;
const auto undoFont = st::historyPremiumViewSet.style.font;
const auto rightSkip = undoFont->width(undoText)
+ st::toastUndoSpace

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_drag_area.h"
#include "history/history_item_components.h"
#include "history/history_item_helpers.h" // GetErrorTextForSending.
#include "history/history_view_swipe.h"
#include "ui/chat/pinned_bar.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/buttons.h"
@@ -399,6 +400,7 @@ RepliesWidget::RepliesWidget(
setupTopicViewer();
setupComposeControls();
setupSwipeReply();
orderWidgets();
if (_pinnedBar) {
@@ -865,6 +867,66 @@ void RepliesWidget::setupComposeControls() {
}
}
void RepliesWidget::setupSwipeReply() {
const auto can = [=](not_null<HistoryItem*> still) {
const auto canSendReply = _topic
? Data::CanSendAnything(_topic)
: Data::CanSendAnything(_history->peer);
const auto allowInAnotherChat = still && still->allowsForward();
if (allowInAnotherChat && (_joinGroup || !canSendReply)) {
return true;
} else if (!_joinGroup && canSendReply) {
return true;
}
return false;
};
HistoryView::SetupSwipeHandler(_inner, _scroll.get(), [=](
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) {
_history->owner().requestItemRepaint(item);
}
}
}, [=, show = controller()->uiShow()](int cursorTop) {
auto result = HistoryView::SwipeHandlerFinishData();
if (_inner->elementInSelectionMode()) {
return result;
}
const auto view = _inner->lookupItemByY(cursorTop);
if (!view->data()->isRegular()
|| view->data()->isService()) {
return result;
}
if (!can(view->data())) {
return result;
}
result.msgBareId = view->data()->fullId().msg.bare;
result.callback = [=, itemId = view->data()->fullId()] {
const auto still = show->session().data().message(itemId);
const auto view = _inner->viewByPosition(still->position());
const auto selected = view->selectedQuote(
_inner->getSelectedTextRange(still));
const auto replyToItemId = (selected.item
? selected.item
: still)->fullId();
_inner->replyToMessageRequestNotify({
.messageId = replyToItemId,
.quote = selected.text,
.quoteOffset = selected.offset,
});
};
return result;
}, _inner->touchMaybeSelectingValue());
}
void RepliesWidget::chooseAttach(
std::optional<bool> overrideSendImagesAsPhotos) {
_choosingAttach = false;
@@ -2631,6 +2693,14 @@ void RepliesWidget::listAddTranslatedItems(
}
}
Ui::ChatPaintContext RepliesWidget::listPreparePaintContext(
Ui::ChatPaintContextArgs &&args) {
auto context = WindowListDelegate::listPreparePaintContext(
std::move(args));
context.gestureHorizontal = _gestureHorizontal;
return context;
}
void RepliesWidget::setupEmptyPainter() {
Expects(_topic != nullptr);

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_memento.h"
#include "history/view/history_view_corner_buttons.h"
#include "history/view/history_view_list_widget.h"
#include "history/history_view_swipe_data.h"
#include "data/data_messages.h"
#include "base/timer.h"
@@ -180,6 +181,8 @@ public:
History *listTranslateHistory() override;
void listAddTranslatedItems(
not_null<TranslateTracker*> tracker) override;
Ui::ChatPaintContext listPreparePaintContext(
Ui::ChatPaintContextArgs &&args) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(
@@ -221,6 +224,7 @@ private:
void finishSending();
void setupComposeControls();
void setupSwipeReply();
void setupRoot();
void setupRootView();
@@ -369,6 +373,8 @@ private:
HistoryView::CornerButtons _cornerButtons;
rpl::lifetime _topicLifetime;
HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal;
int _lastScrollTop = 0;
int _topicReopenBarHeight = 0;
int _scrollTopDelta = 0;

View File

@@ -86,7 +86,8 @@ rpl::producer<Ui::RequestsBarContent> RequestsBarContentByPeer(
state->someUserpicsNotLoaded = false;
for (auto &userpic : state->userpics) {
userpic.peer->loadUserpic();
auto image = userpic.peer->generateUserpicImage(
auto image = PeerData::GenerateUserpicImage(
userpic.peer,
userpic.view,
userpicSize * style::DevicePixelRatio());
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);

View File

@@ -154,7 +154,7 @@ void StickerToast::showWithTitle(const QString &title) {
? tr::lng_animated_emoji_saved_open(tr::now)
: tr::lng_sticker_premium_view(tr::now);
_st.padding.setLeft(skip + size + skip);
_st.padding.setRight(st::historyPremiumViewSet.font->width(view)
_st.padding.setRight(st::historyPremiumViewSet.style.font->width(view)
- st::historyPremiumViewSet.width);
clearHiddenHiding();

Some files were not shown because too many files have changed in this diff Show More