Compare commits

..

83 Commits

Author SHA1 Message Date
John Preston
476e66d027 Version 6.3.3: Fix build with Xcode. 2025-11-21 20:37:46 +04:00
John Preston
fc11d81673 Version 6.3.3.
- Some more improvements for gift auctions.
2025-11-21 19:07:31 +04:00
John Preston
629754a353 Correctly track emoji pausing in suggestions bar. 2025-11-21 19:05:02 +04:00
John Preston
147dbee051 Implement active auctions chats list bar. 2025-11-21 18:58:03 +04:00
John Preston
7204c3c25d Version 6.3.2: Fix build with GCC. 2025-11-20 23:58:26 +04:00
23rd
b412241d25 Added minimal threshold to emoji preview for reaction selector. 2025-11-20 22:39:01 +04:00
23rd
f9883afd61 Removed assertion from stats point details widget to avoid crash. 2025-11-20 22:39:01 +04:00
GitHub Action
181f811f18 Update User-Agent for DNS to Chrome 142.0.0.0. 2025-11-20 22:25:19 +04:00
dependabot[bot]
2f0bd3c085 Bump endersonmenezes/free-disk-space from 2.1.1 to 3.0.0
Bumps [endersonmenezes/free-disk-space](https://github.com/endersonmenezes/free-disk-space) from 2.1.1 to 3.0.0.
- [Release notes](https://github.com/endersonmenezes/free-disk-space/releases)
- [Commits](713d134e24...6c4664f433)

---
updated-dependencies:
- dependency-name: endersonmenezes/free-disk-space
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-20 22:25:03 +04:00
Ilya Fedin
a9d8332766 macos-15-intel -> macos-latest 2025-11-20 22:24:51 +04:00
John Preston
f5036171cf Add actions menu (about/copy/share) for auctions. 2025-11-20 22:24:16 +04:00
John Preston
cdc8b8e473 Show nice userpics in change recipient. 2025-11-20 21:53:35 +04:00
John Preston
ab404c5452 Fix possible crash in sticker preview. 2025-11-20 21:48:30 +04:00
John Preston
0585f9d667 Show correct value of gifted auction gift. 2025-11-20 21:12:20 +04:00
John Preston
19225c7dd3 Rebuild slider when relevant values change. 2025-11-20 20:47:56 +04:00
23rd
8c1844b1c0 Improved unloading heavy part from MediaGeneric for gift's backgrounds. 2025-11-20 19:25:44 +03:00
23rd
af63d86e24 Replaced timer with single-shot updates in auction from view web page. 2025-11-20 19:02:55 +03:00
23rd
1f2fd3ad96 Added support of auctions to history view web page. 2025-11-20 18:22:43 +03:00
23rd
f41fdcdb98 Added saved music to data export. 2025-11-20 15:43:21 +03:00
23rd
f78a9b4220 Removed display of empty menu from userpic in profile top bar. 2025-11-20 15:43:21 +03:00
23rd
06b3ce58ed Fixed stories mouse interaction after album switch in My Profile.
When switching between cached albums with equal story counts,
stories become unresponsive until scrolling occurs. This happens
because the list is recreated with zero visible top/bottom values.

Added deferred visible range update after album switching.
2025-11-20 15:43:20 +03:00
John Preston
7c70d8b1c2 Show some rounds info on details page. 2025-11-20 15:01:26 +04:00
John Preston
2b1e032a9b Show by color if you'll be winning the round. 2025-11-20 14:34:19 +04:00
23rd
4f5d6a2fd5 Replaced dependence from scroll events in flexible scroll with custom. 2025-11-20 14:15:56 +04:00
23rd
c3b90aa492 Improved calculation of filler height for flexible scroll. 2025-11-20 14:15:56 +04:00
23rd
bb2daac007 Added support of text color from emoji status in profile top bar. 2025-11-20 14:15:56 +04:00
23rd
bc2449c3f9 Fixed update of local state after reorder stories in albums. 2025-11-20 14:15:56 +04:00
23rd
0bf50de77a Fixed display of menu for actions in profile top bar for sublists. 2025-11-20 14:15:56 +04:00
23rd
473bc32b71 Fixed peer in info profile opened from sublist. 2025-11-20 14:15:55 +04:00
23rd
e5e143dcf8 Extended behavior of manage button in profile top bar to edit topic. 2025-11-20 14:15:55 +04:00
23rd
2de08746ac Added toast to topic icon in profile top bar. 2025-11-20 14:15:55 +04:00
23rd
ae6833b4d5 Added width checks to profile top bar to prevent redundant calls. 2025-11-20 14:15:55 +04:00
23rd
f1fe5f6a71 Fixed width of very long go-to-forum button in profile top bar. 2025-11-20 14:15:55 +04:00
23rd
5054d0615e Fixed events handler of self forwards tagger.
Regression was introduced in e62a4b065a.
2025-11-20 14:15:55 +04:00
John Preston
4f5007ea64 Use a separate lang key for reset photo button. 2025-11-20 14:14:08 +04:00
John Preston
233f6ed13b Improve bid information display. 2025-11-20 14:13:43 +04:00
John Preston
f15b883471 Support subtext in Stars Bubble in bids. 2025-11-20 13:29:19 +04:00
John Preston
312d5f0121 Improve auction gift display. 2025-11-20 11:39:22 +04:00
Ilya Fedin
75a1657c49 Log Linux notification daemon startup errors 2025-11-20 10:42:13 +04:00
Ilya Fedin
667d92100e Switch Linux notification service watcher to std::optional 2025-11-20 10:42:13 +04:00
Ilya Fedin
8ff4bc8cff Port IconButton back to setAccessibleName 2025-11-19 16:06:56 +04:00
John Preston
656b262648 Allow setting custom auction bid. 2025-11-19 15:00:06 +04:00
John Preston
c1769b9ba2 Correctly show/hide price selection. 2025-11-18 20:30:43 +04:00
John Preston
04c9d92b4a Fix couple of comments layout bugs. 2025-11-18 20:11:22 +04:00
John Preston
0babef5a09 Always show comments, send stars if disabled. 2025-11-18 19:44:24 +04:00
John Preston
cd52407723 Fix back button restore in new profile cover. 2025-11-18 18:24:16 +04:00
John Preston
68c0aa7fb9 Fix quadratic loop on many same custom emoji. 2025-11-18 17:57:00 +04:00
John Preston
f1622c40a4 Fix possible crash from accessibility text insertion. 2025-11-18 17:56:33 +04:00
John Preston
688e7316eb Version 6.3.1.
- Reorder saved music in My Profile.
- Fix new cover overlapping information in My Profile.
- Fix opening stories from My Profile.
- Fix media viewer breaking after viewing Live Stories.
- Fix several possible crashes.
2025-11-17 20:45:32 +04:00
23rd
ef5ec47797 Slightly improved code style of info media list section. 2025-11-17 18:30:36 +03:00
23rd
147ad4a773 Slightly improved code style of info media list widget. 2025-11-17 18:30:36 +03:00
23rd
9752145b49 Fixed userpic paint on new self upload in profile top bar. 2025-11-17 18:30:36 +03:00
23rd
662b862d2f Fixed edge skips in list of actions from info profile. 2025-11-17 18:30:36 +03:00
23rd
6d469509a4 Added tooltip to userpic in profile top bar. 2025-11-17 18:30:36 +03:00
23rd
0dfbb8a5ae Added ability to set group or channel photo from profile top bar. 2025-11-17 18:30:36 +03:00
23rd
1e12ecda70 Extended story outline support to channels in profile top bar. 2025-11-17 18:30:36 +03:00
23rd
035087987c Added icon for reorder to media list section with one column mode. 2025-11-17 18:30:36 +03:00
23rd
18422c4193 Added ability to get both item and section by point in layout overview. 2025-11-17 18:30:36 +03:00
John Preston
f1f9fe27a9 Provide better error on specific bad proxy links.
There are a lot of incorrect MTProxy links with secret in base64
starting with "EE" in base64 encoded format.
2025-11-17 19:26:06 +04:00
John Preston
10667e14e2 Fix crash in ShareBox scheduling.
Fixes #30012.
2025-11-17 18:36:43 +04:00
John Preston
b04c7efdf4 Fix last server message tracking in sublist/forum.
Fixes #30011.
2025-11-17 18:15:16 +04:00
John Preston
0119731360 Fix style of username input for a proxy. 2025-11-17 18:15:00 +04:00
John Preston
8f337684d5 Add close buttons to AuctionGotGiftsBox. 2025-11-17 13:58:19 +04:00
John Preston
dae93552f0 Show correct months count in premium gifts. 2025-11-17 13:57:18 +04:00
John Preston
351bbb240f Shorten bid amount for huge bids. 2025-11-17 13:44:16 +04:00
John Preston
5716de2e6e Fix LIVE badge translations. 2025-11-17 13:03:53 +04:00
John Preston
14295d59d1 Add some debug logs on app startup timing. 2025-11-17 12:55:44 +04:00
John Preston
df0849473c Fix possible crash in button accessibility. 2025-11-17 12:22:54 +04:00
John Preston
9736706894 Fix initial loading of My Profile with stories. 2025-11-17 12:19:23 +04:00
John Preston
12343a5c31 Less templates in Info::FlexibleScroll. 2025-11-17 12:02:42 +04:00
John Preston
a7f046a617 Fix incorrect GL texture deletion. 2025-11-17 11:23:40 +04:00
Ilya Fedin
67bf796f1e Split scheme check code in Linux launch maps implementation 2025-11-17 10:48:01 +04:00
John Preston
22d632abc3 Update hidden gift icon. 2025-11-17 10:47:07 +04:00
John Preston
25094c1ee6 Support paddings in icon parts. 2025-11-17 10:47:07 +04:00
John Preston
5b71ad0456 Fix build with MSVC. 2025-11-17 10:47:07 +04:00
John Preston
9146ba996f Force v143 toolset on Windows for now (win7). 2025-11-17 09:39:25 +04:00
23rd
42900787e1 Added initial ability to filter layout items for reordering. 2025-11-16 23:52:56 +03:00
23rd
ba10c10a94 Added initial ability to reorder tracks in saved music section. 2025-11-16 23:52:56 +03:00
23rd
3f37e9ca6f Added initial ability to reorder stories in albums. 2025-11-16 23:52:56 +03:00
23rd
c5ea86b474 Added initial implementation of reorder items in media layouts.
Except mosaic.
2025-11-16 23:52:56 +03:00
23rd
76720092a5 Added api support to reorder track from saved music. 2025-11-16 23:52:56 +03:00
23rd
7a75c80b27 Attempted to improve status color with darkest gifts in profile top bar. 2025-11-16 13:01:57 +03:00
23rd
b2dcbebb5b Added proper visibility forwarding to flexible scroll content widget. 2025-11-16 12:54:15 +03:00
150 changed files with 4247 additions and 942 deletions

View File

@@ -40,7 +40,7 @@ jobs:
macos:
name: MacOS
runs-on: macos-15-intel
runs-on: macos-latest
strategy:
matrix:

View File

@@ -61,7 +61,7 @@ jobs:
sudo lxd waitready
- name: Free up some disk space.
uses: endersonmenezes/free-disk-space@713d134e243b926eba4a5cce0cf608bfd1efb89a
uses: endersonmenezes/free-disk-space@6c4664f43348c8c7011b53488d5ca65e9fc5cd1a
with:
remove_android: true
remove_dotnet: true

View File

@@ -440,6 +440,9 @@ div.toast_shown {
.section.stories {
background-image: url(../images/section_stories.png);
}
.section.music {
background-image: url(../images/section_music.png);
}
.section.web {
background-image: url(../images/section_web.png);
}
@@ -481,6 +484,16 @@ div.toast_shown {
.media_video .fill {
background-image: url(../images/media_video.png)
}
.audio_icon {
width: 48px;
height: 48px;
border-radius: 50%;
background-color: #4f9cd9;
background-image: url(../images/media_music.png);
background-repeat: no-repeat;
background-position: 12px 12px;
background-size: 24px 24px;
}
@media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
.section.calls {
@@ -504,6 +517,9 @@ div.toast_shown {
.section.stories {
background-image: url(../images/section_stories@2x.png);
}
.section.music {
background-image: url(../images/section_music@2x.png);
}
.section.web {
background-image: url(../images/section_web@2x.png);
}
@@ -545,6 +561,9 @@ div.toast_shown {
.media_video .fill {
background-image: url(../images/media_video@2x.png)
}
.audio_icon {
background-image: url(../images/media_music@2x.png);
}
}
.spoiler {

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -328,6 +328,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_proxy_box_password" = "Password";
"lng_proxy_invalid" = "The proxy link is invalid.";
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
"lng_proxy_incorrect_secret" = "This proxy link uses invalid **secret** parameter. Please contact the proxy provider and ask him to update MTProxy source code and configure it with a correct **secret** value. Then let him provide a new link.";
"lng_edit_deleted" = "This message was deleted";
"lng_edit_limit_reached#one" = "You've reached the message text limit. Please make the text shorter by {count} character.";
@@ -1591,6 +1592,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_administrators#one" = "{count} administrator";
"lng_profile_administrators#other" = "{count} administrators";
"lng_profile_manage" = "Channel settings";
"lng_profile_topic_toast" = "This topic contains {name}";
"lng_invite_upgrade_title" = "Upgrade to Premium";
"lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users.";
@@ -1682,9 +1684,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_suggest_photo" = "Suggest Profile Photo";
"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
"lng_profile_set_photo_for" = "Set Profile Photo";
"lng_profile_set_photo_for_group" = "Set Group Photo";
"lng_profile_set_photo_for_channel" = "Set Channel Photo";
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
"lng_profile_set_photo_for_about" = "You can replace {user}'s photo with another photo that only you will see.";
"lng_profile_photo_reset" = "Reset to Original";
"lng_profile_photo_reset_button" = "Reset";
"lng_profile_photo_reset_sure" = "Are you sure you want to reset {user}'s photo to the original?";
"lng_profile_photo_from_clipboard" = "From clipboard";
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
@@ -2290,7 +2295,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}";
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
"lng_action_gift_self_auction" = "You've successfully bought a gift in the auction for {cost}.";
"lng_action_gift_auction" = "You've successfully bought a gift for {name} in the auction for {cost}.";
"lng_action_gift_auction_won" = "You won the auction with a bid of {cost}.";
"lng_action_gift_self_subtitle" = "Saved Gift";
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
@@ -3998,6 +4003,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_auction_about_top_bidders#other" = "top {count} bidders";
"lng_auction_about_top_about#one" = "{count} gift is dropped in {rounds} to the {bidders} by bid amount.";
"lng_auction_about_top_about#other" = "{count} gifts are dropped in {rounds} to the {bidders} by bid amount.";
"lng_auction_about_top_short#one" = "{count} gift is dropped to the {bidders} by bid amount. {link}";
"lng_auction_about_top_short#other" = "{count} gifts are dropped to the {bidders} by bid amount. {link}";
"lng_auction_about_bid_title" = "Bid Carryover";
"lng_auction_about_bid_about#one" = "If your bid leaves the top {count}, it will automatically join the next round.";
"lng_auction_about_bid_about#other" = "If your bid leaves the top {count}, it will automatically join the next round.";
@@ -4027,7 +4034,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_auction_menu_copy_link" = "Copy Link";
"lng_auction_menu_share" = "Share";
"lng_auction_bid_title" = "Place a Bid";
"lng_auction_bid_subtitle#one" = "Top {count} bidder will win";
"lng_auction_bid_subtitle#other" = "Top {count} bidders will win";
"lng_auction_bid_your" = "your bid";
"lng_auction_bid_custom" = "click to bid more";
"lng_auction_bid_threshold#one" = "TOP {count}";
"lng_auction_bid_threshold#other" = "TOP {count}";
"lng_auction_bid_minimal#one" = "minimum bid";
@@ -4045,6 +4055,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_auction_bid_increased_title" = "Your bid has been increased";
"lng_auction_bid_done_text#one" = "If you fall below the **top {count}**, your bid will roll over to the next round.";
"lng_auction_bid_done_text#other" = "If you fall below the **top {count}**, your bid will roll over to the next round.";
"lng_auction_bid_custom_title" = "Custom Amount";
"lng_auction_preview_left#one" = "{count} left";
"lng_auction_preview_left#other" = "{count} left";
"lng_auction_preview_join" = "Join";
@@ -6293,6 +6304,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices.";
"lng_export_option_stories" = "Story archive";
"lng_export_option_stories_about" = "All stories you posted from Telegram mobile apps.";
"lng_export_option_profile_music" = "Music on Profiles";
"lng_export_option_profile_music_about" = "All tracks you saved to your playlist.";
"lng_export_option_sessions" = "Active sessions";
"lng_export_option_sessions_about" = "We may store this to display your connected devices in Settings > Privacy & Security > Show all sessions.";
"lng_export_header_other" = "Other";

View File

@@ -31,6 +31,8 @@
<file alias="images/section_contacts@2x.png">../../export_html/images/section_contacts@2x.png</file>
<file alias="images/section_frequent.png">../../export_html/images/section_frequent.png</file>
<file alias="images/section_frequent@2x.png">../../export_html/images/section_frequent@2x.png</file>
<file alias="images/section_music.png">../../export_html/images/section_music.png</file>
<file alias="images/section_music@2x.png">../../export_html/images/section_music@2x.png</file>
<file alias="images/section_other.png">../../export_html/images/section_other.png</file>
<file alias="images/section_other@2x.png">../../export_html/images/section_other@2x.png</file>
<file alias="images/section_photos.png">../../export_html/images/section_photos.png</file>

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_stickers.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_chat.h"
@@ -43,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_send_action.h"
#include "data/data_stories.h"
#include "data/data_message_reactions.h"
@@ -1701,7 +1703,13 @@ void Updates::feedUpdate(const MTPUpdate &update) {
local->history()->peer,
local->date());
}
local->setRealId(d.vid().v);
local->setRealId(newId);
if (const auto topic = local->topic()) {
topic->applyMaybeLast(local);
}
if (const auto sublist = local->savedSublist()) {
sublist->applyMaybeLast(local);
}
}
}
} else {

View File

@@ -378,10 +378,8 @@ connectionPortInputField: InputField(defaultInputField) {
width: 55px;
}
connectionUserInputField: InputField(defaultInputField) {
width: 95px;
}
connectionPasswordInputField: InputField(defaultInputField) {
width: 120px;
}
connectionIPv6Skip: 11px;

View File

@@ -115,6 +115,7 @@ void AddProxyFromClipboard(
Success,
Failed,
Unsupported,
IncorrectSecret,
Invalid,
};
@@ -154,8 +155,11 @@ void AddProxyFromClipboard(
qthelp::UrlParamNameTransform::ToLower);
const auto proxy = ProxyDataFromFields(type, fields);
if (!proxy) {
return (proxy.status() == ProxyData::Status::Unsupported)
const auto status = proxy.status();
return (status == ProxyData::Status::Unsupported)
? Result::Unsupported
: (status == ProxyData::Status::IncorrectSecret)
? Result::IncorrectSecret
: Result::Invalid;
}
const auto contains = controller->contains(proxy);
@@ -189,9 +193,11 @@ void AddProxyFromClipboard(
tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
} else {
show->showBox(Ui::MakeInformBox(
(success == Result::Unsupported
? tr::lng_proxy_unsupported(tr::now)
: tr::lng_proxy_invalid(tr::now))));
((success == Result::IncorrectSecret)
? tr::lng_proxy_incorrect_secret(tr::now, tr::rich)
: (success == Result::Unsupported)
? tr::lng_proxy_unsupported(tr::now, tr::rich)
: tr::lng_proxy_invalid(tr::now, tr::rich))));
}
}
}
@@ -1183,7 +1189,7 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) {
}
void ProxyBox::setupCredentials(const ProxyData &data) {
_credentials = _content->add(
_credentials = _content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_content,
object_ptr<Ui::VerticalLayout>(_content)));
@@ -1316,10 +1322,13 @@ void ProxiesBoxController::ShowApplyConfirmation(
const QMap<QString, QString> &fields) {
const auto proxy = ProxyDataFromFields(type, fields);
if (!proxy) {
const auto status = proxy.status();
auto box = Ui::MakeInformBox(
(proxy.status() == ProxyData::Status::Unsupported
? tr::lng_proxy_unsupported(tr::now)
: tr::lng_proxy_invalid(tr::now)));
((status == ProxyData::Status::Unsupported)
? tr::lng_proxy_unsupported(tr::now, tr::rich)
: (status == ProxyData::Status::IncorrectSecret)
? tr::lng_proxy_incorrect_secret(tr::now, tr::rich)
: tr::lng_proxy_invalid(tr::now, tr::rich)));
if (controller) {
controller->uiShow()->showBox(std::move(box));
} else {

View File

@@ -316,8 +316,17 @@ void AddUniqueGiftPropertyRows(
const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars) {
auto helper = Ui::Text::CustomEmojiHelper();
const auto addUpgradeToValue = !entry.credits.ton()
&& !entry.giftUpgradeGifted
&& !entry.giftUpgradeSeparate
&& entry.starsUpgradedBySender;
const auto amount = addUpgradeToValue
? CreditsAmount(
entry.credits.whole() + entry.starsUpgradedBySender,
entry.credits.nano())
: entry.credits;
const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji(
)).append(' ').append(Lang::FormatCreditsAmountDecimal(entry.credits));
)).append(' ').append(Lang::FormatCreditsAmountDecimal(amount));
auto label = object_ptr<Ui::FlatLabel>(
table,
rpl::single(price),
@@ -1328,6 +1337,15 @@ void AddStarGiftTable(
PeerId(entry.bareEntryOwnerId)),
st::giveawayGiftCodePeerMargin);
}
} else if (entry.auction && entry.bareGiftOwnerId) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer(),
MakePeerTableValue(
table,
show,
PeerId(entry.bareGiftOwnerId)),
st::giveawayGiftCodePeerMargin);
} else if (peerId && !giftToSelf) {
const auto user = session->data().peer(peerId)->asUser();
const auto withSendButton = entry.in && user && !user->isBot();

View File

@@ -658,7 +658,7 @@ void Controller::setupPhotoButtons() {
_window->session().api().peerPhoto().clearPersonal(_user);
close();
},
.confirmText = tr::lng_profile_photo_reset(tr::now),
.confirmText = tr::lng_profile_photo_reset_button(tr::now),
}));
});

View File

@@ -577,6 +577,7 @@ object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to,
UserpicsTransferType type) {
using Type = UserpicsTransferType;
struct State {
std::vector<not_null<PeerData*>> from;
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
@@ -676,27 +677,28 @@ object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
button->render(&q, position, QRegion(), QWidget::DrawChildren);
}
state->painting = false;
const auto boosting = (type == UserpicsTransferType::BoostReplace);
const auto last = state->buttons.back().get();
const auto back = boosting ? last : right;
const auto add = st::boostReplaceIconAdd;
const auto &icon = boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
const auto y = back->y() + back->height() - h + add.y();
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Ui::Premium::ButtonGradientStops());
q.setBrush(brush);
pen.setWidthF(stroke);
q.setPen(pen);
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
icon.paint(q, x + skip, y + skip, outerw);
if (type != Type::AuctionRecipient) {
const auto boosting = (type == Type::BoostReplace);
const auto back = boosting ? last : right;
const auto add = st::boostReplaceIconAdd;
const auto &icon = boosting
? st::boostReplaceIcon
: st::starrefJoinIcon;
const auto skip = boosting ? st::boostReplaceIconSkip : 0;
const auto w = icon.width() + 2 * skip;
const auto h = icon.height() + 2 * skip;
const auto x = back->x() + back->width() - w + add.x();
const auto y = back->y() + back->height() - h + add.y();
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Ui::Premium::ButtonGradientStops());
q.setBrush(brush);
pen.setWidthF(stroke);
q.setPen(pen);
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
icon.paint(q, x + skip, y + skip, outerw);
}
const auto size = st::boostReplaceArrow.size();
st::boostReplaceArrow.paint(
q,
@@ -705,7 +707,6 @@ object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
+ (st::boostReplaceUserpicsSkip - size.width()) / 2),
(last->height() - size.height()) / 2,
outerw);
q.end();
auto p = QPainter(overlay);

View File

@@ -64,6 +64,7 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
enum class UserpicsTransferType {
BoostReplace,
StarRefJoin,
AuctionRecipient,
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateUserpicsTransfer(
not_null<Ui::RpWidget*> parent,

View File

@@ -586,7 +586,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
uiShow()->showBox(
HistoryView::PrepareScheduleBox(
this,
nullptr, // ChatHelpers::Show for effect attachment.
_descriptor.session,
sendMenuDetails(),
[=](Api::SendOptions options) { submit(options); },
action.options,

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ class Show;
namespace Data {
struct GiftAuctionState;
struct ActiveAuctions;
} // namespace Data
namespace Info::PeerGifts {
@@ -27,6 +28,7 @@ namespace Ui {
class BoxContent;
class RoundButton;
class GenericBox;
[[nodiscard]] rpl::lifetime ShowStarGiftAuction(
not_null<Window::SessionController*> controller,
@@ -53,4 +55,24 @@ void SetAuctionButtonCountdownText(
AuctionButtonCountdownType type,
rpl::producer<Data::GiftAuctionState> value);
void AuctionAboutBox(
not_null<GenericBox*> box,
int rounds,
int giftsPerRound,
Fn<void(Fn<void()> close)> understood);
[[nodiscard]] TextWithEntities ActiveAuctionsTitle(
const Data::ActiveAuctions &auctions);
struct ManyAuctionsState {
TextWithEntities text;
bool someOutbid = false;
};
[[nodiscard]] ManyAuctionsState ActiveAuctionsState(
const Data::ActiveAuctions &auctions);
[[nodiscard]] rpl::producer<TextWithEntities> ActiveAuctionsButton(
const Data::ActiveAuctions &auctions);
[[nodiscard]] Fn<void()> ActiveAuctionsCallback(
not_null<Window::SessionController*> window,
const Data::ActiveAuctions &auctions);
} // namespace Ui

View File

@@ -1519,11 +1519,12 @@ void AddUpgradeButton(
}
void AddSoldLeftSlider(
not_null<RoundButton*> button,
const GiftTypeStars &gift) {
not_null<RpWidget*> above,
const GiftTypeStars &gift,
QMargins added = {}) {
const auto still = gift.info.limitedLeft;
const auto total = gift.info.limitedCount;
const auto slider = CreateChild<RpWidget>(button->parentWidget());
const auto slider = CreateChild<RpWidget>(above->parentWidget());
struct State {
Text::String still;
Text::String sold;
@@ -1540,13 +1541,13 @@ void AddSoldLeftSlider(
state->height = st::giftLimitedPadding.top()
+ st::semiboldFont->height
+ st::giftLimitedPadding.bottom();
button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
above->geometryValue() | rpl::start_with_next([=](QRect geometry) {
const auto space = st::giftLimitedBox.buttonPadding.top();
const auto skip = (space - state->height) / 2;
slider->setGeometry(
geometry.x(),
geometry.x() + added.left(),
geometry.y() - skip - state->height,
geometry.width(),
geometry.width() - added.left() - added.right(),
state->height);
}, slider->lifetime());
slider->paintRequest() | rpl::start_with_next([=] {
@@ -4455,6 +4456,7 @@ void SendGiftBox(
const GiftDescriptor &descriptor,
rpl::producer<Data::GiftAuctionState> auctionState) {
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
const auto auction = !!auctionState;
const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0);
@@ -4465,7 +4467,7 @@ void SendGiftBox(
: Api::DisallowedGiftTypes();
const auto disallowLimited = !peer->isSelf()
&& (disallowed & Api::DisallowedGiftType::Limited);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setStyle((limited && !auction) ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title());
box->addTopButton(st::boxTitleClose, [=] {
@@ -4737,7 +4739,47 @@ void SendGiftBox(
SendGift(window, peer, api, details, done);
});
if (limited) {
AddSoldLeftSlider(button, *stars);
if (auction) {
const auto &now = state->auction.current();
const auto rounds = now.totalRounds;
const auto perRound = now.gift->auctionGiftsPerRound;
auto owned = object_ptr<Ui::FlatLabel>(
container,
rpl::single(tr::lng_auction_about_top_short(
tr::now,
lt_count,
perRound,
lt_bidders,
tr::lng_auction_about_top_bidders(
tr::now,
lt_count,
perRound,
tr::rich),
lt_link,
tr::lng_auction_text_link(
tr::now,
lt_arrow,
Text::IconEmoji(&st::textMoreIconEmoji),
tr::link),
tr::rich)),
st::defaultDividerLabel.label);
const auto label = owned.data();
const auto about = container->add(
object_ptr<Ui::DividerLabel>(
container,
std::move(owned),
st::defaultBoxDividerLabelPadding),
{ 0, st::giftLimitedBox.buttonPadding.top(), 0, 0 });
AddSoldLeftSlider(about, *stars, st::boxRowPadding);
const auto show = window->uiShow();
label->setClickHandlerFilter([=](const auto &...) {
show->show(Box(AuctionAboutBox, rounds, perRound, nullptr));
return false;
});
} else {
AddSoldLeftSlider(button, *stars);
}
}
if (stars && stars->info.auction()) {
SetAuctionButtonCountdownText(

View File

@@ -1727,11 +1727,7 @@ groupCallTopReactorBadge: RoundCheckbox(defaultRoundCheckbox) {
border: groupCallMembersBg;
}
confcallLinkMenu: IconButton(boxTitleClose) {
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
}
groupCallLinkMenu: IconButton(confcallLinkMenu) {
groupCallLinkMenu: IconButton(boxTitleMenu) {
icon: icon {{ "title_menu_dots", groupCallMemberInactiveIcon }};
iconOver: icon {{ "title_menu_dots", groupCallMemberInactiveIcon }};
ripple: RippleAnimation(defaultRippleAnimation) {

View File

@@ -163,6 +163,7 @@ void Panel::Incoming::RendererGL::init(QOpenGLFunctions &f) {
}
void Panel::Incoming::RendererGL::deinit(QOpenGLFunctions *f) {
_controlsShadowImage.destroy(f);
_textures.destroy(f);
_imageProgram = std::nullopt;
_texturedVertexShader = nullptr;

View File

@@ -279,7 +279,7 @@ void ShowConferenceCallLinkBox(
if (!args.initial && call->canManage()) {
const auto toggle = Ui::CreateChild<Ui::IconButton>(
close->parentWidget(),
st.menuToggle ? *st.menuToggle : st::confcallLinkMenu);
st.menuToggle ? *st.menuToggle : st::boxTitleMenu);
const auto handler = [=] {
if (state->resetting) {
return;

View File

@@ -164,6 +164,7 @@ void Messages::send(TextWithTags text, int stars) {
_sendingIdByRandomId.emplace(randomId, localId);
const auto from = _call->messagesFrom();
const auto creator = _real->creator();
const auto skip = skipMessage(prepared, stars);
if (skip) {
_skippedIds.emplace(localId);
@@ -173,7 +174,7 @@ void Messages::send(TextWithTags text, int stars) {
.peer = from,
.text = std::move(prepared),
.stars = stars,
.admin = (from == _call->peer()),
.admin = (from == _call->peer()) || (creator && from->isSelf()),
.mine = true,
});
}
@@ -233,7 +234,8 @@ void Messages::received(const MTPDupdateGroupCallMessage &data) {
fields.vfrom_id(),
fields.vmessage(),
fields.vdate().v,
fields.vpaid_message_stars().value_or_empty());
fields.vpaid_message_stars().value_or_empty(),
fields.is_from_admin());
}
void Messages::received(const MTPDupdateGroupCallEncryptedMessage &data) {
@@ -268,6 +270,7 @@ void Messages::received(const MTPDupdateGroupCallEncryptedMessage &data) {
deserialized->message,
base::unixtime::now(), // date
0, // stars
false,
true); // checkCustomEmoji
}
@@ -332,6 +335,7 @@ void Messages::received(
const MTPTextWithEntities &message,
TimeId date,
int stars,
bool fromAdmin,
bool checkCustomEmoji) {
const auto peer = _call->peer();
const auto i = ranges::find(_messages, id, &Message::id);
@@ -381,7 +385,7 @@ void Messages::received(
.peer = author,
.text = std::move(text),
.stars = stars,
.admin = (author == _call->peer()),
.admin = fromAdmin,
.mine = mine,
});
ranges::sort(_messages, ranges::less(), &Message::id);

View File

@@ -145,6 +145,7 @@ private:
const MTPTextWithEntities &message,
TimeId date,
int stars,
bool fromAdmin,
bool checkCustomEmoji = false);
void sent(uint64 randomId, const MTP::Response &response);
void sent(uint64 randomId, MsgId realId);

View File

@@ -551,7 +551,14 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
entry.left = _streamMode ? 0 : (_width - entry.width) / 2;
entry.textLeft = leftSkip;
entry.textTop = padding.top() + nameHeight;
entry.nameWidth = std::min(entry.width - widthSkip, nameWidth);
entry.nameWidth = std::min(
nameWidth,
(entry.width
- widthSkip
- space
- _liveBadge.maxWidth()
- space
- _adminBadge.maxWidth()));
updateReactionPosition(entry);
const auto contentHeight = entry.textTop + textHeight + padding.bottom();
@@ -1275,6 +1282,7 @@ void MessagesUi::setupMessagesWidget() {
},
.availableWidth = entry.nameWidth,
.palette = &st::groupCallMessagePalette,
.elisionLines = 1,
});
const auto liveLeft = x + textLeft + entry.nameWidth + space;
_liveBadge.draw(p, {

View File

@@ -401,10 +401,12 @@ void Viewport::RendererGL::deinit(QOpenGLFunctions *f) {
_noiseFramebuffer.destroy(f);
for (auto &data : _tileData) {
data.textures.destroy(f);
data.framebuffers.destroy(f);
}
_tileData.clear();
_tileDataIndices.clear();
_buttons.destroy(f);
_names.destroy(f);
}
void Viewport::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {

View File

@@ -451,6 +451,8 @@ emojiPanSlideDuration: 200;
emojiPanArea: size(34px, 32px);
emojiPanRadius: 8px;
emojiPanReactionsPreviewPadding: margins(10px, 20px, 10px, 20px);
emojiPanEmojiPreviewMinHeight: 160px;
emojiPanEmojiPreviewRadius: 8px + 8px + 4px;
defaultTabbedSearchCancel: CrossButton {
width: 33px;

View File

@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "emoji_suggestions_helper.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "settings/settings_premium.h"
@@ -710,12 +711,22 @@ void EmojiListWidget::ensureMediaPreview() {
? controller->sessionController()
: nullptr;
if (sessionController) {
_mediaPreview.create(_mediaPreviewParent, sessionController);
_mediaPreview->setCustomPadding(st::emojiPanReactionsPreviewPadding);
_mediaPreview->setBackgroundMargins(_mediaPreviewMargins);
_mediaPreview->setCornersSkip(st::emojiPanRadius - st::lineWidth);
const auto tooSmall = _mediaPreviewParent->height()
< st::emojiPanEmojiPreviewMinHeight;
const auto parent = tooSmall
? sessionController->content()
: _mediaPreviewParent;
_mediaPreview = base::make_unique_q<Window::MediaPreviewWidget>(
parent,
sessionController);
if (!tooSmall) {
_mediaPreview->setCustomPadding(
st::emojiPanReactionsPreviewPadding);
_mediaPreview->setBackgroundMargins(_mediaPreviewMargins);
_mediaPreview->setCustomRadius(st::emojiPanEmojiPreviewRadius);
}
_mediaPreview->show();
_mediaPreview->setGeometry(_mediaPreviewParent->geometry());
_mediaPreview->setGeometry(parent->geometry());
_mediaPreview->raise();
}
}

View File

@@ -487,7 +487,7 @@ private:
bool _previewShown = false;
object_ptr<Window::MediaPreviewWidget> _mediaPreview = { nullptr };
base::unique_qptr<Window::MediaPreviewWidget> _mediaPreview;
rpl::event_stream<EmojiChosen> _chosen;
rpl::event_stream<FileChosen> _customChosen;

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h"
#include "core/application.h"
#include "base/event_filter.h"
#include "base/integration.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_document.h"
@@ -750,11 +751,11 @@ SuggestionsController::SuggestionsController(
};
_outerFilter.reset(base::install_event_filter(outer, outerCallback));
QObject::connect(
_field,
&QTextEdit::textChanged,
_container,
[=] { handleTextChange(); });
QObject::connect(_field, &QTextEdit::textChanged, _container, [=] {
base::Integration::Instance().enterFromEventLoop([&] {
handleTextChange();
});
});
QObject::connect(
_field,
&QTextEdit::cursorPositionChanged,

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 = 6003000;
constexpr auto AppVersionStr = "6.3";
constexpr auto AppVersion = 6003003;
constexpr auto AppVersionStr = "6.3.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/gift_auctions.h"
#include "api/api_hash.h"
#include "api/api_premium.h"
#include "api/api_text_entities.h"
#include "apiwrap.h"
@@ -18,6 +19,16 @@ namespace Data {
GiftAuctions::GiftAuctions(not_null<Main::Session*> session)
: _session(session)
, _timer([=] { checkSubscriptions(); }) {
crl::on_main(_session, [=] {
rpl::merge(
_session->data().chatsListChanges(),
_session->data().chatsListLoadedEvents()
) | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
requestActive();
}, _lifetime);
});
}
GiftAuctions::~GiftAuctions() = default;
@@ -50,13 +61,27 @@ rpl::producer<GiftAuctionState> GiftAuctions::state(const QString &slug) {
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) {
if (const auto entry = find(data.vgift_id().v)) {
const auto was = myStateKey(entry->state);
apply(entry, data.vstate());
entry->changes.fire({});
if (was != myStateKey(entry->state)) {
_activeChanged.fire({});
}
} else {
requestActive();
}
}
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) {
if (const auto entry = find(data.vgift_id().v)) {
const auto was = myStateKey(entry->state);
apply(entry, data.vuser_state());
entry->changes.fire({});
if (was != myStateKey(entry->state)) {
_activeChanged.fire({});
}
} else {
requestActive();
}
}
@@ -104,6 +129,37 @@ void GiftAuctions::requestAcquired(
}).send();
}
rpl::producer<ActiveAuctions> GiftAuctions::active() const {
return _activeChanged.events_starting_with_copy(
rpl::empty
) | rpl::map([=] {
return collectActive();
});
}
rpl::producer<bool> GiftAuctions::hasActiveChanges() const {
const auto has = hasActive();
return _activeChanged.events(
) | rpl::map([=] {
return hasActive();
}) | rpl::combine_previous(
has
) | rpl::filter([=](bool previous, bool current) {
return previous != current;
}) | rpl::map([=](bool previous, bool current) {
return current;
});
}
bool GiftAuctions::hasActive() const {
for (const auto &[slug, entry] : _map) {
if (myStateKey(entry->state)) {
return true;
}
}
return false;
}
void GiftAuctions::checkSubscriptions() {
const auto now = crl::now();
auto next = crl::time();
@@ -126,6 +182,101 @@ void GiftAuctions::checkSubscriptions() {
}
}
auto GiftAuctions::myStateKey(const GiftAuctionState &state) const
-> MyStateKey {
if (!state.my.bid) {
return {};
}
auto min = 0;
for (const auto &level : state.bidLevels) {
if (level.position > state.gift->auctionGiftsPerRound) {
break;
} else if (!min || min > level.amount) {
min = level.amount;
}
}
return {
.bid = int(state.my.bid),
.position = MyAuctionPosition(state),
.version = state.version,
};
}
ActiveAuctions GiftAuctions::collectActive() const {
auto result = ActiveAuctions();
result.list.reserve(_map.size());
for (const auto &[slug, entry] : _map) {
const auto raw = &entry->state;
if (raw->gift && raw->my.date) {
result.list.push_back(raw);
}
}
return result;
}
uint64 GiftAuctions::countActiveHash() const {
auto result = Api::HashInit();
for (const auto &active : collectActive().list) {
Api::HashUpdate(result, active->version);
Api::HashUpdate(result, active->my.date);
}
return Api::HashFinalize(result);
}
void GiftAuctions::requestActive() {
if (_activeRequestId) {
return;
}
_activeRequestId = _session->api().request(
MTPpayments_GetStarGiftActiveAuctions(MTP_long(countActiveHash()))
).done([=](const MTPpayments_StarGiftActiveAuctions &result) {
result.match([=](const MTPDpayments_starGiftActiveAuctions &data) {
const auto owner = &_session->data();
owner->processUsers(data.vusers());
auto giftsFound = base::flat_set<QString>();
const auto &list = data.vauctions().v;
giftsFound.reserve(list.size());
for (const auto &auction : list) {
const auto &data = auction.data();
auto gift = Api::FromTL(_session, data.vgift());
const auto slug = gift ? gift->auctionSlug : QString();
if (slug.isEmpty()) {
LOG(("Api Error: Bad auction gift."));
continue;
}
auto &entry = _map[slug];
if (!entry) {
entry = std::make_unique<Entry>();
}
const auto raw = entry.get();
if (!raw->state.gift) {
raw->state.gift = std::move(gift);
}
apply(raw, data.vstate());
apply(raw, data.vuser_state());
giftsFound.emplace(slug);
}
for (const auto &[slug, entry] : _map) {
const auto my = &entry->state.my;
if (my->date && !giftsFound.contains(slug)) {
my->to = nullptr;
my->minBidAmount = 0;
my->bid = 0;
my->date = 0;
my->returned = false;
giftsFound.emplace(slug);
}
}
for (const auto &slug : giftsFound) {
_map[slug]->changes.fire({});
}
_activeChanged.fire({});
}, [](const MTPDpayments_starGiftActiveAuctionsNotModified &) {
});
}).send();
}
void GiftAuctions::request(const QString &slug) {
auto &entry = _map[slug];
Assert(entry != nullptr);
@@ -142,6 +293,8 @@ void GiftAuctions::request(const QString &slug) {
raw->requested = false;
const auto &data = result.data();
_session->data().processUsers(data.vusers());
raw->state.gift = Api::FromTL(_session, data.vgift());
if (!raw->state.gift) {
return;
@@ -150,8 +303,7 @@ void GiftAuctions::request(const QString &slug) {
const auto ms = timeout * crl::time(1000);
raw->state.subscribedTill = ms ? (crl::now() + ms) : -1;
_session->data().processUsers(data.vusers());
const auto was = myStateKey(raw->state);
apply(raw, data.vstate());
apply(raw, data.vuser_state());
if (raw->changes.has_consumers()) {
@@ -160,6 +312,9 @@ void GiftAuctions::request(const QString &slug) {
_timer.callOnce(ms);
}
}
if (was != myStateKey(raw->state)) {
_activeChanged.fire({});
}
}).send();
}
@@ -175,49 +330,54 @@ GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const {
void GiftAuctions::apply(
not_null<Entry*> entry,
const MTPStarGiftAuctionState &state) {
Expects(entry->state.gift.has_value());
apply(&entry->state, state);
}
void GiftAuctions::apply(
not_null<GiftAuctionState*> entry,
const MTPStarGiftAuctionState &state) {
Expects(entry->gift.has_value());
const auto raw = &entry->state;
state.match([&](const MTPDstarGiftAuctionState &data) {
const auto version = data.vversion().v;
if (raw->version >= version) {
if (entry->version >= version) {
return;
}
const auto owner = &_session->data();
raw->startDate = data.vstart_date().v;
raw->endDate = data.vend_date().v;
raw->minBidAmount = data.vmin_bid_amount().v;
entry->startDate = data.vstart_date().v;
entry->endDate = data.vend_date().v;
entry->minBidAmount = data.vmin_bid_amount().v;
const auto &levels = data.vbid_levels().v;
raw->bidLevels.clear();
raw->bidLevels.reserve(levels.size());
entry->bidLevels.clear();
entry->bidLevels.reserve(levels.size());
for (const auto &level : levels) {
auto &entry = raw->bidLevels.emplace_back();
auto &bid = entry->bidLevels.emplace_back();
const auto &data = level.data();
entry.amount = data.vamount().v;
entry.position = data.vpos().v;
entry.date = data.vdate().v;
bid.amount = data.vamount().v;
bid.position = data.vpos().v;
bid.date = data.vdate().v;
}
const auto &top = data.vtop_bidders().v;
raw->topBidders.clear();
raw->topBidders.reserve(top.size());
entry->topBidders.clear();
entry->topBidders.reserve(top.size());
for (const auto &user : top) {
raw->topBidders.push_back(owner->user(UserId(user.v)));
entry->topBidders.push_back(owner->user(UserId(user.v)));
}
raw->nextRoundAt = data.vnext_round_at().v;
raw->giftsLeft = data.vgifts_left().v;
raw->currentRound = data.vcurrent_round().v;
raw->totalRounds = data.vtotal_rounds().v;
raw->averagePrice = 0;
entry->nextRoundAt = data.vnext_round_at().v;
entry->giftsLeft = data.vgifts_left().v;
entry->currentRound = data.vcurrent_round().v;
entry->totalRounds = data.vtotal_rounds().v;
entry->averagePrice = 0;
}, [&](const MTPDstarGiftAuctionStateFinished &data) {
raw->averagePrice = data.vaverage_price().v;
raw->startDate = data.vstart_date().v;
raw->endDate = data.vend_date().v;
raw->minBidAmount = 0;
raw->nextRoundAt
= raw->currentRound
= raw->totalRounds
= raw->giftsLeft
= raw->version
entry->averagePrice = data.vaverage_price().v;
entry->startDate = data.vstart_date().v;
entry->endDate = data.vend_date().v;
entry->minBidAmount = 0;
entry->nextRoundAt
= entry->currentRound
= entry->totalRounds
= entry->giftsLeft
= entry->version
= 0;
}, [&](const MTPDstarGiftAuctionStateNotModified &data) {
});
@@ -226,16 +386,32 @@ void GiftAuctions::apply(
void GiftAuctions::apply(
not_null<Entry*> entry,
const MTPStarGiftAuctionUserState &state) {
apply(&entry->state.my, state);
}
void GiftAuctions::apply(
not_null<StarGiftAuctionMyState*> entry,
const MTPStarGiftAuctionUserState &state) {
const auto &data = state.data();
const auto raw = &entry->state.my;
raw->to = data.vbid_peer()
entry->to = data.vbid_peer()
? _session->data().peer(peerFromMTP(*data.vbid_peer())).get()
: nullptr;
raw->minBidAmount = data.vmin_bid_amount().value_or(0);
raw->bid = data.vbid_amount().value_or(0);
raw->date = data.vbid_date().value_or(0);
raw->gotCount = data.vacquired_count().v;
raw->returned = data.is_returned();
entry->minBidAmount = data.vmin_bid_amount().value_or(0);
entry->bid = data.vbid_amount().value_or(0);
entry->date = data.vbid_date().value_or(0);
entry->gotCount = data.vacquired_count().v;
entry->returned = data.is_returned();
}
int MyAuctionPosition(const GiftAuctionState &state) {
const auto &levels = state.bidLevels;
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
if (i->amount < state.my.bid
|| (i->amount == state.my.bid && i->date > state.my.date)) {
return i->position;
}
}
return (levels.empty() ? 0 : levels.back().position) + 1;
}
} // namespace Data

View File

@@ -62,6 +62,10 @@ struct GiftAcquired {
bool nameHidden = false;
};
struct ActiveAuctions {
std::vector<not_null<GiftAuctionState*>> list;
};
class GiftAuctions final {
public:
explicit GiftAuctions(not_null<Main::Session*> session);
@@ -73,31 +77,63 @@ public:
void apply(const MTPDupdateStarGiftAuctionUserState &data);
void requestAcquired(
uint64 giftId,
uint64 giftId,
Fn<void(std::vector<Data::GiftAcquired>)> done);
[[nodiscard]] rpl::producer<ActiveAuctions> active() const;
[[nodiscard]] rpl::producer<bool> hasActiveChanges() const;
[[nodiscard]] bool hasActive() const;
private:
struct Entry {
GiftAuctionState state;
rpl::event_stream<> changes;
bool requested = false;
};
struct MyStateKey {
int bid = 0;
int position = 0;
int version = 0;
explicit operator bool() const {
return bid != 0;
}
friend inline bool operator==(MyStateKey, MyStateKey) = default;
};
void request(const QString &slug);
Entry *find(uint64 giftId) const;
void apply(
not_null<Entry*> entry,
const MTPStarGiftAuctionState &state);
void apply(
not_null<GiftAuctionState*> entry,
const MTPStarGiftAuctionState &state);
void apply(
not_null<Entry*> entry,
const MTPStarGiftAuctionUserState &state);
void apply(
not_null<StarGiftAuctionMyState*> entry,
const MTPStarGiftAuctionUserState &state);
void checkSubscriptions();
[[nodiscard]] MyStateKey myStateKey(const GiftAuctionState &state) const;
[[nodiscard]] ActiveAuctions collectActive() const;
[[nodiscard]] uint64 countActiveHash() const;
void requestActive();
const not_null<Main::Session*> _session;
base::Timer _timer;
base::flat_map<QString, std::unique_ptr<Entry>> _map;
rpl::event_stream<> _activeChanged;
mtpRequestId _activeRequestId = 0;
rpl::lifetime _lifetime;
};
[[nodiscard]] int MyAuctionPosition(const GiftAuctionState &state);
} // namespace Data

View File

@@ -104,11 +104,13 @@ struct CreditsHistoryEntry final {
bool converted : 1 = false;
bool anonymous : 1 = false;
bool stargift : 1 = false;
bool auction : 1 = false;
bool postsSearch : 1 = false;
bool giftTransferred : 1 = false;
bool giftRefunded : 1 = false;
bool giftUpgraded : 1 = false;
bool giftUpgradeSeparate : 1 = false;
bool giftUpgradeGifted : 1 = false;
bool giftResale : 1 = false;
bool giftResaleForceTon : 1 = false;
bool giftPinned : 1 = false;

View File

@@ -836,6 +836,16 @@ void ForumTopic::applyColorId(int32 colorId) {
}
}
void ForumTopic::applyMaybeLast(not_null<HistoryItem*> item) {
if (!_lastServerMessage.value_or(nullptr)
|| (*_lastServerMessage)->id < item->id) {
setLastServerMessage(item);
resolveChatListMessageGroup();
} else {
growLastKnownServerMessageId(item->id);
}
}
void ForumTopic::applyItemAdded(not_null<HistoryItem*> item) {
if (item->isRegular()) {
setLastServerMessage(item);

View File

@@ -160,6 +160,7 @@ public:
void applyCreator(PeerId creatorId);
void applyCreationDate(TimeId date);
void applyIsMy(bool my);
void applyMaybeLast(not_null<HistoryItem*> item);
void applyItemAdded(not_null<HistoryItem*> item);
void applyItemRemoved(MsgId id);
void maybeSetLastMessage(not_null<HistoryItem*> item);

View File

@@ -147,6 +147,10 @@ GroupCallOrigin GroupCall::origin() const {
: GroupCallOrigin::Group;
}
bool GroupCall::creator() const {
return _creator;
}
bool GroupCall::canManage() const {
return _conference ? _creator : _peer->canManageGroupCall();
}

View File

@@ -84,6 +84,7 @@ public:
[[nodiscard]] rpl::producer<bool> loadedValue() const;
[[nodiscard]] bool rtmp() const;
[[nodiscard]] GroupCallOrigin origin() const;
[[nodiscard]] bool creator() const;
[[nodiscard]] bool canManage() const;
[[nodiscard]] bool listenersHidden() const;
[[nodiscard]] bool blockchainMayBeEmpty() const;

View File

@@ -2616,7 +2616,9 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
HistoryView::MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftBoxSize.width(),
.paintBg = HistoryView::UniqueGiftBg(message, unique),
.paintBgFactory = [=] {
return HistoryView::UniqueGiftBg(message, unique);
},
.service = true,
});
}

View File

@@ -134,7 +134,7 @@ struct GiveawayResults {
};
enum class GiftType : uchar {
Premium, // count - months
Premium, // count - days
Credits, // count - credits
Ton, // count - nano tons
StarGift, // count - stars
@@ -149,6 +149,7 @@ struct GiftCode {
PeerData *stargiftReleasedBy = nullptr;
std::shared_ptr<UniqueGift> unique;
TextWithEntities message;
PeerData *auctionTo = nullptr;
ChannelData *channel = nullptr;
PeerData *channelFrom = nullptr;
uint64 channelSavedId = 0;
@@ -159,6 +160,7 @@ struct GiftCode {
int starsToUpgrade = 0;
int starsUpgradedBySender = 0;
int starsForDetailsRemove = 0;
int starsBid = 0;
int limitedCount = 0;
int limitedLeft = 0;
int64 count = 0;
@@ -166,6 +168,7 @@ struct GiftCode {
bool viaGiveaway : 1 = false;
bool transferred : 1 = false;
bool upgradeSeparate : 1 = false;
bool upgradeGifted : 1 = false;
bool upgradable : 1 = false;
bool unclaimed : 1 = false;
bool anonymous : 1 = false;

View File

@@ -157,6 +157,38 @@ void SavedMusic::remove(not_null<DocumentData*> document) {
_changed.fire_copy(peerId);
}
void SavedMusic::reorder(int oldPosition, int newPosition) {
const auto peerId = _owner->session().userPeerId();
auto &entry = _entries[peerId];
if (oldPosition < 0 || newPosition < 0
|| oldPosition >= entry.list.size()
|| newPosition >= entry.list.size()
|| oldPosition == newPosition) {
return;
}
const auto item = entry.list[oldPosition];
const auto document = ItemDocument(item);
base::reorder(entry.list, oldPosition, newPosition);
const auto afterDocument = (newPosition > 0)
? ItemDocument(entry.list[newPosition - 1]).get()
: nullptr;
_owner->session().api().request(MTPaccount_SaveMusic(
MTP_flags(afterDocument
? MTPaccount_SaveMusic::Flag::f_after_id
: MTPaccount_SaveMusic::Flags(0)),
document->mtpInput(),
afterDocument ? afterDocument->mtpInput() : MTPInputDocument()
)).done([=] {
}).fail([=](const MTP::Error &error) {
}).send();
_changed.fire_copy(peerId);
}
void SavedMusic::apply(not_null<UserData*> user, const MTPDocument *last) {
const auto peerId = user->id;
auto &entry = _entries[peerId];

View File

@@ -37,6 +37,7 @@ public:
[[nodiscard]] bool has(not_null<DocumentData*> document) const;
void save(not_null<DocumentData*> document, FileOrigin origin);
void remove(not_null<DocumentData*> document);
void reorder(int oldPosition, int newPosition);
void apply(not_null<UserData*> user, const MTPDocument *last);

View File

@@ -187,12 +187,13 @@ rpl::producer<> SavedSublist::destroyed() const {
) | rpl::to_empty);
}
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item, bool added) {
growLastKnownServerMessageId(item->id);
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item) {
if (!_lastServerMessage.value_or(nullptr)
|| (*_lastServerMessage)->id < item->id) {
setLastServerMessage(item);
resolveChatListMessageGroup();
} else {
growLastKnownServerMessageId(item->id);
}
}

View File

@@ -52,8 +52,7 @@ public:
[[nodiscard]] bool isHiddenAuthor() const;
[[nodiscard]] rpl::producer<> destroyed() const;
void growLastKnownServerMessageId(MsgId id);
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
void applyMaybeLast(not_null<HistoryItem*> item);
void applyItemAdded(not_null<HistoryItem*> item);
void applyItemRemoved(MsgId id);
@@ -143,6 +142,7 @@ private:
void setChatListMessage(HistoryItem *item);
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
void growLastKnownServerMessageId(MsgId id);
void changeUnreadCountByMessage(MsgId id, int delta);
void setUnreadCount(std::optional<int> count);

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/mime_type.h" // Core::IsMimeSticker
#include "ui/image/image_location_factory.h" // Images::FromPhotoSize
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/color_int_conversion.h"
#include "export/export_manager.h"
#include "export/view/export_view_panel_controller.h"
#include "mtproto/mtproto_config.h"
@@ -1877,6 +1878,14 @@ rpl::producer<GiftsUpdate> Session::giftsUpdates() const {
return _giftsUpdates.events();
}
void Session::notifyGiftAuctionGot(GiftAuctionGot &&update) {
_giftAuctionGots.fire(std::move(update));
}
rpl::producer<GiftAuctionGot> Session::giftAuctionGots() const {
return _giftAuctionGots.events();
}
HistoryItem *Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) {
const auto list = messagesListForInsert(peerId);
const auto i = list->find(wasId);
@@ -3782,6 +3791,7 @@ not_null<WebPageData*> Session::processWebpage(
nullptr,
nullptr,
nullptr,
nullptr,
0,
QString(),
false,
@@ -3852,6 +3862,7 @@ not_null<WebPageData*> Session::webpage(
std::move(iv),
std::move(stickerSet),
std::move(uniqueGift),
nullptr,
duration,
author,
hasLargeMedia,
@@ -3959,6 +3970,35 @@ void Session::webpageApplyFields(
return nullptr;
};
using WebPageAuctionPtr = std::unique_ptr<WebPageAuction>;
const auto lookupAuction = [&]() -> WebPageAuctionPtr {
const auto toUint = [](const MTPint &c) {
return (uint32(1) << 24) | uint32(c.v);
};
if (const auto attributes = data.vattributes()) {
for (const auto &attribute : attributes->v) {
return attribute.match([&](
const MTPDwebPageAttributeStarGiftAuction &data) {
const auto gift = Api::FromTL(_session, data.vgift());
if (!gift) {
return WebPageAuctionPtr(nullptr);
}
auto auction = std::make_unique<WebPageAuction>();
auction->auctionGift = std::make_shared<StarGift>(*gift);
auction->endDate = data.vend_date().v;
auction->centerColor = Ui::ColorFromSerialized(
toUint(data.vcenter_color()));
auction->edgeColor = Ui::ColorFromSerialized(
toUint(data.vedge_color()));
auction->textColor = Ui::ColorFromSerialized(
toUint(data.vtext_color()));
return auction;
}, [](const auto &) -> WebPageAuctionPtr { return nullptr; });
}
}
return nullptr;
};
auto story = (Data::Story*)nullptr;
auto storyId = FullStoryId();
if (const auto attributes = data.vattributes()) {
@@ -4052,6 +4092,7 @@ void Session::webpageApplyFields(
std::move(iv),
lookupStickerSet(),
lookupUniqueGift(),
lookupAuction(),
data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
@@ -4074,6 +4115,7 @@ void Session::webpageApplyFields(
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
std::unique_ptr<WebPageAuction> auction,
int duration,
const QString &author,
bool hasLargeMedia,
@@ -4094,6 +4136,7 @@ void Session::webpageApplyFields(
std::move(iv),
std::move(stickerSet),
std::move(uniqueGift),
std::move(auction),
duration,
author,
hasLargeMedia,

View File

@@ -19,6 +19,7 @@ class Image;
class HistoryItem;
struct WebPageCollage;
struct WebPageStickerSet;
struct WebPageAuction;
enum class WebPageType : uint8;
enum class NewMessageType;
@@ -115,6 +116,10 @@ struct GiftsUpdate {
std::vector<Data::SavedStarGiftId> added;
std::vector<Data::SavedStarGiftId> removed;
};
struct GiftAuctionGot {
uint64 giftId = 0;
not_null<PeerData*> to;
};
struct SentToScheduled {
not_null<History*> history;
@@ -361,6 +366,8 @@ public:
[[nodiscard]] rpl::producer<GiftUpdate> giftUpdates() const;
void notifyGiftsUpdate(GiftsUpdate &&update);
[[nodiscard]] rpl::producer<GiftsUpdate> giftsUpdates() const;
void notifyGiftAuctionGot(GiftAuctionGot &&update);
[[nodiscard]] rpl::producer<GiftAuctionGot> giftAuctionGots() const;
void requestItemRepaint(not_null<const HistoryItem*> item);
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
void requestViewRepaint(not_null<const ViewElement*> view);
@@ -1016,6 +1023,7 @@ private:
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
std::unique_ptr<WebPageAuction> auction,
int duration,
const QString &author,
bool hasLargeMedia,
@@ -1075,6 +1083,7 @@ private:
rpl::event_stream<not_null<HistoryItem*>> _newItemAdded;
rpl::event_stream<GiftUpdate> _giftUpdates;
rpl::event_stream<GiftsUpdate> _giftsUpdates;
rpl::event_stream<GiftAuctionGot> _giftAuctionGots;
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;

View File

@@ -2006,6 +2006,58 @@ void Stories::albumDelete(not_null<PeerData*> peer, int id) {
}
}
void Stories::albumReorderStories(
not_null<PeerData*> peer,
int albumId,
int oldPosition,
int newPosition,
Fn<void()> done,
Fn<void()> fail) {
const auto ids = albumIds(peer->id, albumId);
const auto list = RespectingPinned(ids);
if (oldPosition < 0 || newPosition < 0
|| oldPosition >= list.size() || newPosition >= list.size()) {
fail();
return;
}
if (_reorderStoriesRequestId) {
_owner->session().api().request(
base::take(_reorderStoriesRequestId)).cancel();
}
auto reorderedList = list;
base::reorder(reorderedList, oldPosition, newPosition);
auto order = QVector<MTPint>();
order.reserve(reorderedList.size());
for (const auto id : reorderedList) {
order.push_back(MTP_int(id));
}
_reorderStoriesRequestId = _owner->session().api().request(
MTPstories_UpdateAlbum(
MTP_flags(MTPstories_UpdateAlbum::Flag::f_order),
peer->input,
MTP_int(albumId),
MTPstring(),
MTPVector<MTPint>(),
MTPVector<MTPint>(),
MTP_vector<MTPint>(order)
)).done([=](const MTPStoryAlbum &result) {
_reorderStoriesRequestId = 0;
if (const auto set = albumIdsSet(peer->id, albumId)) {
set->ids.list = reorderedList;
_albumIdsChanged.fire({ peer->id, albumId });
}
done();
}).fail([=] {
_reorderStoriesRequestId = 0;
fail();
}).send();
}
void Stories::notifyAlbumUpdate(StoryAlbumUpdate &&update) {
const auto peerId = update.peer->id;
const auto i = _albums.find(peerId);

View File

@@ -242,6 +242,13 @@ public:
Fn<void(StoryAlbum)> done,
Fn<void(QString)> fail);
void albumDelete(not_null<PeerData*> peer, int id);
void albumReorderStories(
not_null<PeerData*> peer,
int albumId,
int oldPosition,
int newPosition,
Fn<void()> done,
Fn<void()> fail);
void notifyAlbumUpdate(StoryAlbumUpdate &&update);
[[nodiscard]] rpl::producer<StoryAlbumUpdate> albumUpdates() const;
@@ -477,6 +484,8 @@ private:
base::Timer _pollingTimer;
base::Timer _pollingViewsTimer;
mtpRequestId _reorderStoriesRequestId = 0;
rpl::variable<StealthMode> _stealthMode;
rpl::lifetime _lifetime;

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_star_gift.h"
#include "core/local_url_handlers.h"
#include "lang/lang_keys.h"
#include "iv/iv_data.h"
@@ -177,6 +178,8 @@ WebPageType ParseWebPageType(
return WebPageType::StoryAlbum;
} else if (type == u"telegram_collection"_q) {
return WebPageType::GiftCollection;
} else if (type == u"telegram_auction"_q) {
return WebPageType::Auction;
} else if (hasIV) {
return WebPageType::ArticleWithIV;
} else {
@@ -232,6 +235,7 @@ bool WebPageData::applyChanges(
std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
std::unique_ptr<WebPageAuction> newAuction,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@@ -291,6 +295,7 @@ bool WebPageData::applyChanges(
&& (!iv || iv->partial() == newIv->partial())
&& (!stickerSet == !newStickerSet)
&& (!uniqueGift == !newUniqueGift)
&& (!auction == !newAuction)
&& duration == newDuration
&& author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
@@ -316,6 +321,7 @@ bool WebPageData::applyChanges(
iv = std::move(newIv);
stickerSet = std::move(newStickerSet);
uniqueGift = std::move(newUniqueGift);
auction = std::move(newAuction);
duration = newDuration;
author = resultAuthor;
pendingTill = newPendingTill;

View File

@@ -16,6 +16,7 @@ class ChannelData;
namespace Data {
class Session;
struct UniqueGift;
struct StarGift;
} // namespace Data
namespace Iv {
@@ -51,6 +52,7 @@ enum class WebPageType : uint8 {
StickerSet,
StoryAlbum,
GiftCollection,
Auction,
Article,
ArticleWithIV,
@@ -85,6 +87,14 @@ struct WebPageStickerSet {
};
struct WebPageAuction {
std::shared_ptr<Data::StarGift> auctionGift;
TimeId endDate = 0;
QColor centerColor;
QColor edgeColor;
QColor textColor;
};
struct WebPageData {
WebPageData(not_null<Data::Session*> owner, const WebPageId &id);
~WebPageData();
@@ -106,6 +116,7 @@ struct WebPageData {
std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
std::unique_ptr<WebPageAuction> newAuction,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@@ -137,6 +148,7 @@ struct WebPageData {
std::unique_ptr<Iv::Data> iv;
std::unique_ptr<WebPageStickerSet> stickerSet;
std::shared_ptr<Data::UniqueGift> uniqueGift;
std::unique_ptr<WebPageAuction> auction;
int duration = 0;
TimeId pendingTill = 0;
uint32 version : 29 = 0;

View File

@@ -879,15 +879,15 @@ void CustomEmojiManager::repaintLater(
not_null<Ui::CustomEmoji::Instance*> instance,
Ui::CustomEmoji::RepaintRequest request) {
auto &bunch = _repaints[request.duration];
if (bunch.when < request.when) {
if (bunch.when > 0) {
for (const auto &already : bunch.instances) {
if (already.get() == instance) {
// Still waiting for full bunch repaint, don't bump.
return;
}
if (bunch.when > 0) {
for (const auto &already : bunch.instances) {
if (already.get() == instance) {
// Still waiting for full bunch repaint, don't bump.
return;
}
}
}
if (bunch.when < request.when) {
bunch.when = request.when;
#if 0 // inject-to-on_main
_repaintsLastAdded = request.when;

View File

@@ -129,6 +129,11 @@ dialogRowOpenBot: DialogRightButton {
dialogRowOpenBotRecent: DialogRightButton(dialogRowOpenBot) {
margin: margins(0px, 32px, 16px, 0px);
}
dialogsTopBarRightButton: RoundButton(defaultActiveButton) {
width: -16px;
height: 22px;
textTop: 2px;
}
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};

View File

@@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/call_delayed.h"
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
#include "boxes/star_gift_auction_box.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "data/components/gift_auctions.h"
#include "data/components/promo_suggestions.h"
#include "data/data_birthday.h"
#include "data/data_changes.h"
@@ -184,6 +186,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
rpl::lifetime userpicLifetime;
rpl::lifetime giftsLifetime;
rpl::lifetime creditsLifetime;
rpl::lifetime auctionsLifetime;
std::unique_ptr<Api::CreditsHistory> creditsHistory;
};
@@ -193,8 +196,11 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
rpl::single(st::dialogsTopBarLeftPadding));
const auto ensureContent = [=] {
if (!state->content) {
const auto window = FindSessionController(parent);
state->content = Ui::CreateChild<TopBarSuggestionContent>(
parent);
parent,
[=] { return window->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer); });
rpl::combine(
parent->widthValue(),
state->content->desiredHeightValue()
@@ -229,6 +235,7 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
state->userpicLifetime.destroy();
state->giftsLifetime.destroy();
state->creditsLifetime.destroy();
state->auctionsLifetime.destroy();
if (!session->api().authorizations().unreviewed().empty()) {
state->content = nullptr;
@@ -273,7 +280,50 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
const auto wrap = state->wrap.get();
using RightIcon = TopBarSuggestionContent::RightIcon;
const auto promo = &session->promoSuggestions();
if (const auto custom = promo->custom()) {
const auto auctions = &session->giftAuctions();
if (auctions->hasActive()) {
using namespace Data;
struct Button {
rpl::variable<TextWithEntities> text;
Fn<void()> callback;
base::has_weak_ptr guard;
};
auto &lifetime = state->auctionsLifetime;
const auto button = lifetime.template make_state<Button>();
const auto window = FindSessionController(parent);
auctions->active(
) | rpl::start_with_next([=](ActiveAuctions &&active) {
const auto empty = active.list.empty();
state->desiredWrapToggle.force_assign(
Toggle{ !empty, anim::type::normal });
if (empty) {
return;
}
auto text = Ui::ActiveAuctionsState(active);
const auto textColorOverride = text.someOutbid
? st::attentionButtonFg->c
: std::optional<QColor>();
content->setContent(
Ui::ActiveAuctionsTitle(active),
std::move(text.text),
Core::TextContext({ .session = session }),
textColorOverride);
button->text = Ui::ActiveAuctionsButton(active);
button->callback = Ui::ActiveAuctionsCallback(
window,
active);
}, state->auctionsLifetime);
const auto callback = crl::guard(&button->guard, [=] {
button->callback();
});
content->setRightButton(button->text.value(), callback);
content->setClickedCallback(callback);
content->setLeftPadding(state->leftPadding.value());
state->desiredWrapToggle.force_assign(
Toggle{ true, anim::type::normal });
return;
} else if (const auto custom = promo->custom()) {
content->setRightIcon(RightIcon::Close);
content->setLeftPadding(state->leftPadding.value());
content->setClickedCallback([=] {
@@ -733,12 +783,14 @@ rpl::producer<Ui::SlideWrap<Ui::RpWidget>*> TopBarSuggestionValue(
rpl::merge(
session->promoSuggestions().value(),
session->api().authorizations().unreviewedChanges(),
Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty
Data::AmPremiumValue(session) | rpl::skip(1) | rpl::to_empty,
session->giftAuctions().hasActiveChanges() | rpl::to_empty
) | rpl::start_with_next([=] {
const auto was = state->wrap.get();
const auto weak = base::make_weak(was);
processCurrentSuggestion(processCurrentSuggestion);
if (was != state->wrap) {
consumer.put_next_copy(state->wrap);
if (was != state->wrap || (was && !weak)) {
consumer.put_next_copy(state->wrap.get());
}
}, lifetime);

View File

@@ -156,15 +156,19 @@ not_null<Ui::SlideWrap<Ui::VerticalLayout>*> CreateUnconfirmedAuthContent(
return wrap;
}
TopBarSuggestionContent::TopBarSuggestionContent(not_null<Ui::RpWidget*> p)
: Ui::RippleButton(p, st::defaultRippleAnimationBgOver)
TopBarSuggestionContent::TopBarSuggestionContent(
not_null<Ui::RpWidget*> parent,
Fn<bool()> emojiPaused)
: Ui::RippleButton(parent, st::defaultRippleAnimationBgOver)
, _titleSt(st::semiboldTextStyle)
, _contentTitleSt(st::dialogsTopBarSuggestionTitleStyle)
, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle) {
, _contentTextSt(st::dialogsTopBarSuggestionAboutStyle)
, _emojiPaused(std::move(emojiPaused)) {
setRightIcon(RightIcon::Close);
}
void TopBarSuggestionContent::setRightIcon(RightIcon icon) {
_rightButton = nullptr;
if (icon == _rightIcon) {
return;
}
@@ -201,6 +205,35 @@ void TopBarSuggestionContent::setRightIcon(RightIcon icon) {
}
}
void TopBarSuggestionContent::setRightButton(
rpl::producer<TextWithEntities> text,
Fn<void()> callback) {
_rightHide = nullptr;
_rightArrow = nullptr;
_rightIcon = RightIcon::None;
if (!text) {
_rightButton = nullptr;
return;
}
using namespace Ui;
_rightButton = base::make_unique_q<RoundButton>(
this,
rpl::single(QString()),
st::dialogsTopBarRightButton);
_rightButton->setText(std::move(text));
rpl::combine(
sizeValue(),
_rightButton->sizeValue()
) | rpl::start_with_next([=](QSize outer, QSize inner) {
const auto top = (outer.height() - inner.height()) / 2;
_rightButton->moveToRight(top, top, outer.width());
}, _rightButton->lifetime());
_rightButton->setFullRadius(true);
_rightButton->setTextTransform(RoundButton::TextTransform::NoTransform);
_rightButton->setClickedCallback(std::move(callback));
_rightButton->show();
}
void TopBarSuggestionContent::draw(QPainter &p) {
const auto kLinesForPhoto = 3;
@@ -226,6 +259,8 @@ void TopBarSuggestionContent::draw(QPainter &p) {
- (_rightHide ? _rightHide->width() : 0);
const auto titleRight = leftPadding;
const auto hasSecondLineTitle = availableWidth < _contentTitle.maxWidth();
const auto paused = On(PowerSaving::kEmojiChat)
|| (_emojiPaused && _emojiPaused());
p.setPen(st::windowActiveTextFg);
p.setPen(st::windowFg);
{
@@ -237,7 +272,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
? availableWidth
: (availableWidth - titleRight),
.availableWidth = availableWidth,
.pausedEmoji = On(PowerSaving::kEmojiChat),
.pausedEmoji = paused,
.elisionLines = hasSecondLineTitle ? 2 : 1,
});
}
@@ -270,7 +305,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
: availableWidth,
};
};
p.setPen(st::windowSubTextFg);
p.setPen(_descriptionColorOverride.value_or(st::windowSubTextFg->c));
_contentText.draw(p, {
.position = QPoint(left, top),
.outerWidth = availableWidth,
@@ -278,7 +313,7 @@ void TopBarSuggestionContent::draw(QPainter &p) {
.geometry = Ui::Text::GeometryDescriptor{
.layout = std::move(lineLayout),
},
.pausedEmoji = On(PowerSaving::kEmojiChat),
.pausedEmoji = paused,
});
_lastPaintedContentTop = top;
_lastPaintedContentLineAmount = lastContentLineAmount;
@@ -288,7 +323,9 @@ void TopBarSuggestionContent::draw(QPainter &p) {
void TopBarSuggestionContent::setContent(
TextWithEntities title,
TextWithEntities description,
std::optional<Ui::Text::MarkedContext> context) {
std::optional<Ui::Text::MarkedContext> context,
std::optional<QColor> descriptionColorOverride) {
_descriptionColorOverride = descriptionColorOverride;
if (context) {
context->repaint = [=] { update(); };
_contentTitle.setMarkedText(
@@ -305,6 +342,7 @@ void TopBarSuggestionContent::setContent(
_contentTitle.setMarkedText(_contentTitleSt, std::move(title));
_contentText.setMarkedText(_contentTextSt, std::move(description));
}
update();
}
void TopBarSuggestionContent::paintEvent(QPaintEvent *) {

View File

@@ -40,17 +40,23 @@ public:
Arrow,
};
TopBarSuggestionContent(not_null<Ui::RpWidget*>);
TopBarSuggestionContent(
not_null<Ui::RpWidget*> parent,
Fn<bool()> emojiPaused = nullptr);
void setContent(
TextWithEntities title,
TextWithEntities description,
std::optional<Ui::Text::MarkedContext> context = std::nullopt);
std::optional<Ui::Text::MarkedContext> context = std::nullopt,
std::optional<QColor> descriptionColorOverride = std::nullopt);
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
void setHideCallback(Fn<void()>);
void setRightIcon(RightIcon);
void setRightButton(
rpl::producer<TextWithEntities> text,
Fn<void()> callback);
void setLeftPadding(rpl::producer<int>);
[[nodiscard]] const style::TextStyle &contentTitleSt() const;
@@ -69,10 +75,13 @@ private:
Ui::Text::String _contentText;
rpl::variable<int> _lastPaintedContentLineAmount = 0;
rpl::variable<int> _lastPaintedContentTop = 0;
std::optional<QColor> _descriptionColorOverride;
base::unique_qptr<Ui::IconButton> _rightHide;
base::unique_qptr<Ui::IconButton> _rightArrow;
base::unique_qptr<Ui::RoundButton> _rightButton;
Fn<void()> _hideCallback;
Fn<bool()> _emojiPaused;
int _leftPadding = 0;

View File

@@ -89,6 +89,10 @@ struct StoriesInfo {
int count = 0;
};
struct ProfileMusicInfo {
int count = 0;
};
struct FileLocation {
int dcId = 0;
MTPInputFileLocation data;
@@ -929,6 +933,11 @@ StoriesSlice ParseStoriesSlice(
const MTPVector<MTPStoryItem> &data,
int baseIndex);
struct ProfileMusicSlice {
std::vector<Message> list;
int skipped = 0;
};
Message ParseMessage(
ParseMediaContext &context,
const MTPMessage &data,

View File

@@ -32,6 +32,7 @@ constexpr auto kFileMaxSize = 4000 * int64(1024 * 1024);
constexpr auto kLocationCacheSize = 100'000;
constexpr auto kMaxEmojiPerRequest = 100;
constexpr auto kStoriesSliceLimit = 100;
constexpr auto kProfileMusicSliceLimit = 100;
struct LocationKey {
uint64 type;
@@ -112,6 +113,7 @@ struct ApiWrap::StartProcess {
enum class Step {
UserpicsCount,
StoriesCount,
ProfileMusicCount,
SplitRanges,
DialogsCount,
LeftChannelsCount,
@@ -155,6 +157,19 @@ struct ApiWrap::StoriesProcess {
int fileIndex = 0;
};
struct ApiWrap::ProfileMusicProcess {
FnMut<bool(Data::ProfileMusicInfo&&)> start;
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::ProfileMusicSlice&&)> handleSlice;
FnMut<void()> finish;
int processed = 0;
std::optional<Data::ProfileMusicSlice> slice;
int offsetId = 0;
bool lastSlice = false;
int fileIndex = 0;
};
struct ApiWrap::OtherDataProcess {
Data::File file;
FnMut<void(Data::File&&)> done;
@@ -438,6 +453,9 @@ void ApiWrap::startExport(
if (_settings->types & Settings::Type::Stories) {
_startProcess->steps.push_back(Step::StoriesCount);
}
if (_settings->types & Settings::Type::ProfileMusic) {
_startProcess->steps.push_back(Step::ProfileMusicCount);
}
if (_settings->types & Settings::Type::AnyChatsMask) {
_startProcess->steps.push_back(Step::SplitRanges);
_startProcess->steps.push_back(Step::DialogsCount);
@@ -468,6 +486,8 @@ void ApiWrap::sendNextStartRequest() {
return requestUserpicsCount();
case Step::StoriesCount:
return requestStoriesCount();
case Step::ProfileMusicCount:
return requestProfileMusicCount();
case Step::SplitRanges:
return requestSplitRanges();
case Step::DialogsCount:
@@ -518,6 +538,34 @@ void ApiWrap::requestStoriesCount() {
}).send();
}
void ApiWrap::requestProfileMusicCount() {
Expects(_startProcess != nullptr);
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(0), // offset
MTP_int(0), // limit
MTP_long(0) // hash
)).done([=](const MTPusers_SavedMusic &result) {
Expects(_settings != nullptr);
Expects(_startProcess != nullptr);
const auto count = result.match(
[](const MTPDusers_savedMusic &data) {
return data.vcount().v;
}, [](const MTPDusers_savedMusicNotModified &data) {
return -1;
});
if (count < 0) {
error("Unexpected messagesNotModified received.");
return;
}
_startProcess->info.profileMusicCount = count;
sendNextStartRequest();
}).send();
}
void ApiWrap::requestSplitRanges() {
Expects(_startProcess != nullptr);
@@ -1062,6 +1110,215 @@ void ApiWrap::finishStories() {
base::take(_storiesProcess)->finish();
}
void ApiWrap::requestProfileMusic(
FnMut<bool(Data::ProfileMusicInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::ProfileMusicSlice&&)> slice,
FnMut<void()> finish) {
Expects(_profileMusicProcess == nullptr);
_profileMusicProcess = std::make_unique<ProfileMusicProcess>();
_profileMusicProcess->start = std::move(start);
_profileMusicProcess->fileProgress = std::move(progress);
_profileMusicProcess->handleSlice = std::move(slice);
_profileMusicProcess->finish = std::move(finish);
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(0), // offset
MTP_int(kProfileMusicSliceLimit), // limit
MTP_long(0) // hash
)).done([=](const MTPusers_SavedMusic &result) mutable {
Expects(_profileMusicProcess != nullptr);
auto startInfo = result.match(
[](const MTPDusers_savedMusic &data) {
return Data::ProfileMusicInfo{ data.vcount().v };
}, [](const MTPDusers_savedMusicNotModified &data) {
return Data::ProfileMusicInfo{ 0 };
});
if (!_profileMusicProcess->start(std::move(startInfo))) {
return;
}
handleProfileMusicSlice(result);
}).send();
}
void ApiWrap::handleProfileMusicSlice(const MTPusers_SavedMusic &result) {
Expects(_profileMusicProcess != nullptr);
Expects(_selfId.has_value());
auto context = Data::ParseMediaContext();
context.selfPeerId = peerFromUser(*_selfId);
auto slice = result.match([&](const MTPDusers_savedMusic &data) {
if (data.vdocuments().v.size() < kProfileMusicSliceLimit) {
_profileMusicProcess->lastSlice = true;
}
auto result = Data::MessagesSlice();
for (const auto &doc : data.vdocuments().v) {
auto message = Data::Message();
message.id = ++_profileMusicProcess->processed;
message.date = 0;
message.media.content = Data::ParseDocument(
context,
doc,
"profile_music/",
0);
result.list.push_back(std::move(message));
}
return result;
}, [&](const MTPDusers_savedMusicNotModified &) {
_profileMusicProcess->lastSlice = true;
return Data::MessagesSlice();
});
auto profileSlice = Data::ProfileMusicSlice();
profileSlice.list.reserve(slice.list.size());
for (auto &message : slice.list) {
if (v::is<Data::Document>(message.media.content)) {
const auto &doc = v::get<Data::Document>(message.media.content);
if (doc.isAudioFile) {
profileSlice.list.push_back(std::move(message));
}
}
}
loadProfileMusicFiles(std::move(profileSlice));
}
void ApiWrap::loadProfileMusicFiles(Data::ProfileMusicSlice &&slice) {
Expects(_profileMusicProcess != nullptr);
Expects(!_profileMusicProcess->slice.has_value());
if (slice.list.empty()) {
_profileMusicProcess->lastSlice = true;
}
_profileMusicProcess->slice = std::move(slice);
_profileMusicProcess->fileIndex = 0;
loadNextProfileMusic();
}
void ApiWrap::loadNextProfileMusic() {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
for (auto &list = _profileMusicProcess->slice->list
; _profileMusicProcess->fileIndex < list.size()
; ++_profileMusicProcess->fileIndex) {
auto &message = list[_profileMusicProcess->fileIndex];
const auto origin = Data::FileOrigin{ .messageId = message.id };
const auto ready = processFileLoad(
message.file(),
origin,
[=](FileProgress value) { return loadProfileMusicProgress(value); },
[=](const QString &path) { loadProfileMusicDone(path); },
&message);
if (!ready) {
return;
}
const auto thumbProgress = [=](FileProgress value) {
return loadProfileMusicThumbProgress(value);
};
const auto thumbReady = processFileLoad(
message.thumb().file,
origin,
thumbProgress,
[=](const QString &path) { loadProfileMusicThumbDone(path); },
&message);
if (!thumbReady) {
return;
}
}
finishProfileMusicSlice();
}
void ApiWrap::finishProfileMusicSlice() {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
auto slice = *base::take(_profileMusicProcess->slice);
if (!slice.list.empty()) {
_profileMusicProcess->processed += slice.list.size();
_profileMusicProcess->offsetId = slice.list.back().id;
if (!_profileMusicProcess->handleSlice(std::move(slice))) {
return;
}
}
if (_profileMusicProcess->lastSlice) {
finishProfileMusic();
return;
}
mainRequest(MTPusers_GetSavedMusic(
_user,
MTP_int(_profileMusicProcess->offsetId),
MTP_int(kProfileMusicSliceLimit),
MTP_long(0)
)).done([=](const MTPusers_SavedMusic &result) {
handleProfileMusicSlice(result);
}).send();
}
bool ApiWrap::loadProfileMusicProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
return _profileMusicProcess->fileProgress(DownloadProgress{
_fileProcess->randomId,
_fileProcess->relativePath,
_profileMusicProcess->fileIndex,
progress.ready,
progress.total });
}
void ApiWrap::loadProfileMusicDone(const QString &relativePath) {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
const auto index = _profileMusicProcess->fileIndex;
auto &file = _profileMusicProcess->slice->list[index].file();
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextProfileMusic();
}
bool ApiWrap::loadProfileMusicThumbProgress(FileProgress progress) {
return loadProfileMusicProgress(progress);
}
void ApiWrap::loadProfileMusicThumbDone(const QString &relativePath) {
Expects(_profileMusicProcess != nullptr);
Expects(_profileMusicProcess->slice.has_value());
Expects((_profileMusicProcess->fileIndex >= 0)
&& (_profileMusicProcess->fileIndex
< _profileMusicProcess->slice->list.size()));
const auto index = _profileMusicProcess->fileIndex;
auto &file = _profileMusicProcess->slice->list[index].thumb().file;
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextProfileMusic();
}
void ApiWrap::finishProfileMusic() {
Expects(_profileMusicProcess != nullptr);
base::take(_profileMusicProcess)->finish();
}
void ApiWrap::requestContacts(FnMut<void(Data::ContactsList&&)> done) {
Expects(_contactsProcess == nullptr);

View File

@@ -21,6 +21,8 @@ struct UserpicsInfo;
struct UserpicsSlice;
struct StoriesInfo;
struct StoriesSlice;
struct ProfileMusicInfo;
struct ProfileMusicSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
@@ -50,6 +52,7 @@ public:
struct StartInfo {
int userpicsCount = 0;
int storiesCount = 0;
int profileMusicCount = 0;
int dialogsCount = 0;
};
void startExport(
@@ -86,6 +89,12 @@ public:
Fn<bool(Data::StoriesSlice&&)> slice,
FnMut<void()> finish);
void requestProfileMusic(
FnMut<bool(Data::ProfileMusicInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::ProfileMusicSlice&&)> slice,
FnMut<void()> finish);
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
@@ -109,6 +118,7 @@ private:
struct ContactsProcess;
struct UserpicsProcess;
struct StoriesProcess;
struct ProfileMusicProcess;
struct OtherDataProcess;
struct FileProcess;
struct FileProgress;
@@ -121,6 +131,7 @@ private:
void sendNextStartRequest();
void requestUserpicsCount();
void requestStoriesCount();
void requestProfileMusicCount();
void requestSplitRanges();
void requestDialogsCount();
void requestLeftChannelsCount();
@@ -146,6 +157,16 @@ private:
void finishStoriesSlice();
void finishStories();
void handleProfileMusicSlice(const MTPusers_SavedMusic &result);
void loadProfileMusicFiles(Data::ProfileMusicSlice &&slice);
void loadNextProfileMusic();
bool loadProfileMusicProgress(FileProgress value);
void loadProfileMusicDone(const QString &relativePath);
bool loadProfileMusicThumbProgress(FileProgress value);
void loadProfileMusicThumbDone(const QString &relativePath);
void finishProfileMusicSlice();
void finishProfileMusic();
void otherDataDone(const QString &relativePath);
bool useOnlyLastSplit() const;
@@ -258,6 +279,7 @@ private:
std::unique_ptr<ContactsProcess> _contactsProcess;
std::unique_ptr<UserpicsProcess> _userpicsProcess;
std::unique_ptr<StoriesProcess> _storiesProcess;
std::unique_ptr<ProfileMusicProcess> _profileMusicProcess;
std::unique_ptr<OtherDataProcess> _otherDataProcess;
std::unique_ptr<FileProcess> _fileProcess;
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;

View File

@@ -76,6 +76,7 @@ private:
void exportPersonalInfo();
void exportUserpics();
void exportStories();
void exportProfileMusic();
void exportContacts();
void exportSessions();
void exportOtherData();
@@ -91,6 +92,7 @@ private:
ProcessingState statePersonalInfo() const;
ProcessingState stateUserpics(const DownloadProgress &progress) const;
ProcessingState stateStories(const DownloadProgress &progress) const;
ProcessingState stateProfileMusic(const DownloadProgress &progress) const;
ProcessingState stateContacts() const;
ProcessingState stateSessions() const;
ProcessingState stateOtherData() const;
@@ -119,6 +121,9 @@ private:
int _storiesWritten = 0;
int _storiesCount = 0;
int _profileMusicWritten = 0;
int _profileMusicCount = 0;
// rpl::variable<State> fails to compile in MSVC :(
State _state;
rpl::event_stream<State> _stateChanges;
@@ -281,6 +286,9 @@ void ControllerObject::fillExportSteps() {
if (_settings.types & Type::Stories) {
_steps.push_back(Step::Stories);
}
if (_settings.types & Type::ProfileMusic) {
_steps.push_back(Step::ProfileMusic);
}
if (_settings.types & Type::Contacts) {
_steps.push_back(Step::Contacts);
}
@@ -317,6 +325,9 @@ void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::Stories) {
push(Step::Stories, 1);
}
if (_settings.types & Settings::Type::ProfileMusic) {
push(Step::ProfileMusic, 1);
}
if (_settings.types & Settings::Type::Contacts) {
push(Step::Contacts, 1);
}
@@ -356,6 +367,7 @@ void ControllerObject::exportNext() {
case Step::PersonalInfo: return exportPersonalInfo();
case Step::Userpics: return exportUserpics();
case Step::Stories: return exportStories();
case Step::ProfileMusic: return exportProfileMusic();
case Step::Contacts: return exportContacts();
case Step::Sessions: return exportSessions();
case Step::OtherData: return exportOtherData();
@@ -454,6 +466,32 @@ void ControllerObject::exportStories() {
});
}
void ControllerObject::exportProfileMusic() {
_api.requestProfileMusic([=](Data::ProfileMusicInfo &&start) {
if (ioCatchError(_writer->writeProfileMusicStart(start))) {
return false;
}
_profileMusicWritten = 0;
_profileMusicCount = start.count;
return true;
}, [=](DownloadProgress progress) {
setState(stateProfileMusic(progress));
return true;
}, [=](Data::ProfileMusicSlice &&slice) {
if (ioCatchError(_writer->writeProfileMusicSlice(slice))) {
return false;
}
_profileMusicWritten += slice.list.size();
setState(stateProfileMusic(DownloadProgress()));
return true;
}, [=] {
if (ioCatchError(_writer->writeProfileMusicEnd())) {
return;
}
exportNext();
});
}
void ControllerObject::exportContacts() {
setState(stateContacts());
_api.requestContacts([=](Data::ContactsList &&result) {
@@ -596,6 +634,21 @@ ProcessingState ControllerObject::stateStories(
});
}
ProcessingState ControllerObject::stateProfileMusic(
const DownloadProgress &progress) const {
return prepareState(Step::ProfileMusic, [&](ProcessingState &result) {
result.entityIndex = _profileMusicWritten + progress.itemIndex;
result.entityCount = std::max(_profileMusicCount, result.entityIndex);
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
}
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
});
}
ProcessingState ControllerObject::stateContacts() const {
return prepareState(Step::Contacts);
}

View File

@@ -39,6 +39,7 @@ struct ProcessingState {
PersonalInfo,
Userpics,
Stories,
ProfileMusic,
Contacts,
Sessions,
OtherData,

View File

@@ -58,6 +58,7 @@ struct Settings {
PrivateChannels = 0x200,
PublicChannels = 0x400,
Stories = 0x800,
ProfileMusic = 0x1000,
GroupsMask = PrivateGroups | PublicGroups,
ChannelsMask = PrivateChannels | PublicChannels,
@@ -68,6 +69,7 @@ struct Settings {
| Userpics
| Contacts
| Stories
| ProfileMusic
| Sessions),
AllMask = NonChatsMask | OtherData | AnyChatsMask,
};
@@ -97,6 +99,7 @@ struct Settings {
| Type::Userpics
| Type::Contacts
| Type::Stories
| Type::ProfileMusic
| Type::PersonalChats
| Type::PrivateGroups;
}

View File

@@ -16,6 +16,8 @@ struct UserpicsInfo;
struct UserpicsSlice;
struct StoriesInfo;
struct StoriesSlice;
struct ProfileMusicInfo;
struct ProfileMusicSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
@@ -64,6 +66,12 @@ public:
const Data::StoriesSlice &data) = 0;
[[nodiscard]] virtual Result writeStoriesEnd() = 0;
[[nodiscard]] virtual Result writeProfileMusicStart(
const Data::ProfileMusicInfo &data) = 0;
[[nodiscard]] virtual Result writeProfileMusicSlice(
const Data::ProfileMusicSlice &data) = 0;
[[nodiscard]] virtual Result writeProfileMusicEnd() = 0;
[[nodiscard]] virtual Result writeContactsList(
const Data::ContactsList &data) = 0;

View File

@@ -44,9 +44,10 @@ constexpr auto kContactsPriority = 2;
constexpr auto kFrequentContactsPriority = 3;
constexpr auto kUserpicsPriority = 4;
constexpr auto kStoriesPriority = 5;
constexpr auto kSessionsPriority = 6;
constexpr auto kWebSessionsPriority = 7;
constexpr auto kOtherPriority = 8;
constexpr auto kProfileMusicPriority = 6;
constexpr auto kSessionsPriority = 7;
constexpr auto kWebSessionsPriority = 8;
constexpr auto kOtherPriority = 9;
const auto kLineBreak = QByteArrayLiteral("<br>");
@@ -538,6 +539,12 @@ public:
const std::vector<Data::TextPart> &caption,
const QString &internalLinksDomain,
const QString &link = QString());
[[nodiscard]] QByteArray pushAudioEntry(
const QByteArray &name,
const QByteArray &info,
const QByteArrayList &details,
const QByteArray &duration,
const QString &link = QString());
[[nodiscard]] QByteArray pushSessionListEntry(
int apiId,
const QByteArray &name,
@@ -877,6 +884,54 @@ QByteArray HtmlWriter::Wrap::pushStoriesListEntry(
return result;
}
QByteArray HtmlWriter::Wrap::pushAudioEntry(
const QByteArray &name,
const QByteArray &info,
const QByteArrayList &details,
const QByteArray &duration,
const QString &link) {
auto result = pushDiv("entry clearfix");
if (!link.isEmpty()) {
result.append(pushTag("a", {
{ "class", "pull_left userpic_wrap" },
{ "href", relativePath(link).toUtf8() + "#allow_back" },
}));
} else {
result.append(pushDiv("pull_left userpic_wrap"));
}
result.append(pushDiv("userpic audio_icon"));
result.append(popTag());
result.append(popTag());
result.append(pushDiv("body"));
if (!duration.isEmpty()) {
result.append(pushDiv("pull_right info details"));
result.append(SerializeString(duration));
result.append(popTag());
}
if (!info.isEmpty()) {
if (!link.isEmpty()) {
result.append(pushTag("a", {
{ "class", "block_link expanded" },
{ "href", relativePath(link).toUtf8() + "#allow_back" },
}));
}
result.append(pushDiv("name bold"));
result.append(SerializeString(info));
result.append(popTag());
if (!link.isEmpty()) {
result.append(popTag());
}
}
for (const auto &detail : details) {
result.append(pushDiv("details_entry details"));
result.append(SerializeString(detail));
result.append(popTag());
}
result.append(popTag());
result.append(popTag());
return result;
}
QByteArray HtmlWriter::Wrap::pushSessionListEntry(
int apiId,
const QByteArray &name,
@@ -2706,6 +2761,7 @@ Result HtmlWriter::start(
"images/section_chats.png",
"images/section_contacts.png",
"images/section_frequent.png",
"images/section_music.png",
"images/section_other.png",
"images/section_photos.png",
"images/section_sessions.png",
@@ -3001,6 +3057,93 @@ Result HtmlWriter::writeStoriesEnd() {
return Result::Success();
}
Result HtmlWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
Expects(_summary != nullptr);
Expects(_profileMusic == nullptr);
_profileMusicCount = data.count;
if (!_profileMusicCount) {
return Result::Success();
}
_profileMusic = fileWithRelativePath(profileMusicFilePath());
auto block = _profileMusic->pushHeader(
"Profile Music",
mainFileRelativePath());
block.append(_profileMusic->pushDiv("page_body list_page"));
block.append(_profileMusic->pushDiv("entry_list"));
if (const auto result = _profileMusic->writeBlock(block); !result) {
return result;
}
return Result::Success();
}
Result HtmlWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
Expects(_profileMusic != nullptr);
_profileMusicCount -= data.skipped;
if (data.list.empty()) {
return Result::Success();
}
auto block = QByteArray();
for (const auto &message : data.list) {
if (!v::is<Data::Document>(message.media.content)) {
continue;
}
const auto &doc = v::get<Data::Document>(message.media.content);
if (!doc.isAudioFile) {
continue;
}
using SkipReason = Data::File::SkipReason;
const auto &file = doc.file;
Assert(!file.relativePath.isEmpty()
|| file.skipReason != SkipReason::None);
auto status = QByteArrayList();
status.append([&]() -> Data::Utf8String {
switch (file.skipReason) {
case SkipReason::Unavailable:
return "(File unavailable, please try again later)";
case SkipReason::FileSize:
return "(File exceeds maximum size. "
"Change data exporting settings to download.)";
case SkipReason::FileType:
return "(File not included. "
"Change data exporting settings to download.)";
case SkipReason::None: return Data::FormatFileSize(file.size);
}
Unexpected("Skip reason while writing profile music path.");
}());
const auto &path = file.relativePath;
const auto title = !doc.songTitle.isEmpty()
? doc.songTitle
: !doc.name.isEmpty()
? doc.name
: Data::Utf8String("Unknown Track");
const auto performer = !doc.songPerformer.isEmpty()
? doc.songPerformer
: Data::Utf8String("Unknown Artist");
const auto info = performer + " - " + title;
const auto duration = doc.duration > 0
? Data::FormatDuration(doc.duration)
: QByteArray();
block.append(_profileMusic->pushAudioEntry(
(path.isEmpty() ? QString("File unavailable") : path).toUtf8(),
info,
status,
duration,
path));
}
return _profileMusic->writeBlock(block);
}
Result HtmlWriter::writeProfileMusicEnd() {
pushProfileMusicSection();
if (_profileMusic) {
return base::take(_profileMusic)->close();
}
return Result::Success();
}
QString HtmlWriter::storiesFilePath() const {
return "lists/stories.html";
}
@@ -3014,6 +3157,19 @@ void HtmlWriter::pushStoriesSection() {
storiesFilePath());
}
QString HtmlWriter::profileMusicFilePath() const {
return "lists/profile_music.html";
}
void HtmlWriter::pushProfileMusicSection() {
pushSection(
kProfileMusicPriority,
"Profile Music",
"music",
_profileMusicCount,
profileMusicFilePath());
}
Result HtmlWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_summary != nullptr);

View File

@@ -64,6 +64,10 @@ public:
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;
@@ -131,9 +135,11 @@ private:
const QString &userpicPath);
void pushUserpicsSection();
void pushStoriesSection();
void pushProfileMusicSection();
[[nodiscard]] QString userpicsFilePath() const;
[[nodiscard]] QString storiesFilePath() const;
[[nodiscard]] QString profileMusicFilePath() const;
[[nodiscard]] QByteArray wrapMessageLink(
int messageId,
@@ -159,6 +165,9 @@ private:
int _storiesCount = 0;
std::unique_ptr<Wrap> _stories;
int _profileMusicCount = 0;
std::unique_ptr<Wrap> _profileMusic;
QString _dialogsRelativePath;
Data::DialogInfo _dialog;
DialogsMode _dialogsMode = DialogsMode::None;

View File

@@ -73,6 +73,24 @@ Result HtmlAndJsonWriter::writeStoriesEnd() {
});
}
Result HtmlAndJsonWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicStart(data);
});
}
Result HtmlAndJsonWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicSlice(data);
});
}
Result HtmlAndJsonWriter::writeProfileMusicEnd() {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicEnd();
});
}
Result HtmlAndJsonWriter::writeContactsList(const Data::ContactsList &data) {
return invoke([&](WriterPtr w) {
return w->writeContactsList(data);

View File

@@ -36,6 +36,10 @@ public:
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;

View File

@@ -1318,6 +1318,34 @@ Result JsonWriter::writeStoriesEnd() {
return _output->writeBlock(popNesting());
}
Result JsonWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
Expects(_output != nullptr);
auto block = prepareObjectItemStart("profile_music");
return _output->writeBlock(block + pushNesting(Context::kArray));
}
Result JsonWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
Expects(_output != nullptr);
if (data.list.empty()) {
return Result::Success();
}
auto block = QByteArray();
for (const auto &message : data.list) {
block.append(prepareArrayItemStart());
block.append(SerializeMessage(_context, message, {}, QString()));
}
return _output->writeBlock(block);
}
Result JsonWriter::writeProfileMusicEnd() {
Expects(_output != nullptr);
return _output->writeBlock(popNesting());
}
Result JsonWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_output != nullptr);

View File

@@ -48,6 +48,10 @@ public:
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;

View File

@@ -96,6 +96,13 @@ Content ContentFromState(
state.bytesName,
state.bytesRandomId);
break;
case Step::ProfileMusic:
pushMain(tr::lng_export_option_profile_music(tr::now));
pushBytes(
"music" + QString::number(state.entityIndex),
state.bytesName,
state.bytesRandomId);
break;
case Step::Sessions:
pushMain(tr::lng_export_option_sessions(tr::now));
break;

View File

@@ -183,6 +183,11 @@ void SettingsWidget::setupFullExportOptions(
tr::lng_export_option_stories(tr::now),
Type::Stories,
tr::lng_export_option_stories_about(tr::now));
addOptionWithAbout(
container,
tr::lng_export_option_profile_music(tr::now),
Type::ProfileMusic,
tr::lng_export_option_profile_music_about(tr::now));
addHeader(container, tr::lng_export_header_chats(tr::now));
addOption(
container,
@@ -233,7 +238,8 @@ void SettingsWidget::setupMediaOptions(
| Type::PrivateGroups
| Type::PrivateChannels
| Type::PublicGroups
| Type::PublicChannels)) != 0, anim::type::normal);
| Type::PublicChannels
| Type::ProfileMusic)) != 0, anim::type::normal);
}, mediaWrap->lifetime());
widthValue(

View File

@@ -752,7 +752,7 @@ not_null<HistoryItem*> History::addNewItem(
}
if (const auto sublist = item->savedSublist()) {
sublist->applyMaybeLast(item, unread);
sublist->applyMaybeLast(item);
}
return item;
@@ -1373,6 +1373,13 @@ void History::applyServiceChanges(
}
}
}
}, [&](const MTPDmessageActionStarGift &data) {
if (data.is_auction_acquired() && data.vto_id()) {
const auto to = peer->owner().peer(peerFromMTP(*data.vto_id()));
data.vgift().match([&](const MTPDstarGift &data) {
peer->owner().notifyGiftAuctionGot({ data.vid().v, to });
}, [](const auto &) {});
}
}, [](const auto &) {
});
}

View File

@@ -6065,29 +6065,15 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
}
}
} else if (anonymous || _history->peer->isSelf()) {
const auto to = (action.is_auction_acquired() && action.vto_id())
? peer->owner().peer(peerFromMTP(*action.vto_id())).get()
: nullptr;
result.text = to
? tr::lng_action_gift_auction(
result.text = (action.is_auction_acquired()
? tr::lng_action_gift_auction_won
: anonymous
? tr::lng_action_gift_received_anonymous
: tr::lng_action_gift_self_bought)(
tr::now,
lt_name,
Ui::Text::Link(to->shortName(), 1),
lt_cost,
cost,
Ui::Text::WithEntities)
: (action.is_auction_acquired()
? tr::lng_action_gift_self_auction
: anonymous
? tr::lng_action_gift_received_anonymous
: tr::lng_action_gift_self_bought)(
tr::now,
lt_cost,
cost,
Ui::Text::WithEntities);
if (to) {
result.links.push_back(to->createOpenLink());
}
tr::marked);
} else if (upgradeGifted) {
// Who sent the gift.
const auto fromId = action.vfrom_id()
@@ -6636,6 +6622,14 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
: PeerId();
const auto upgradeMsgId = data.vupgrade_msg_id().value_or_empty();
const auto realGiftMsgId = data.vgift_msg_id().value_or_empty();
const auto bid = data.vgift().match([&](const MTPDstarGift &gift) {
return data.is_auction_acquired()
? (int(gift.vstars().v)
+ int(gift.vupgrade_stars().value_or_empty()))
: 0;
}, [](const MTPDstarGiftUnique &) {
return 0;
});
using Fields = Data::GiftCode;
auto fields = Fields{
.message = (data.vmessage()
@@ -6643,6 +6637,12 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
&history()->session(),
*data.vmessage())
: TextWithEntities()),
.auctionTo = (service
&& data.is_auction_acquired()
&& data.vto_id())
? history()->owner().peer(
peerFromMTP(*data.vto_id())).get()
: nullptr,
.channel = ((service && peerIsChannel(to))
? history()->owner().channel(peerToChannel(to)).get()
: nullptr),
@@ -6656,8 +6656,10 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
.starsConverted = int(data.vconvert_stars().value_or_empty()),
.starsUpgradedBySender = int(
data.vupgrade_stars().value_or_empty()),
.starsBid = bid,
.type = Data::GiftType::StarGift,
.upgradeSeparate = data.is_upgrade_separate(),
.upgradeGifted = data.is_prepaid_upgrade(),
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(),
.converted = data.is_converted(),

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/notify/data_notify_settings.h"
#include "data/data_changes.h"
#include "data/data_drafts.h"
#include "data/data_group_call.h"
#include "data/data_messages.h"
#include "data/data_message_reactions.h"
#include "data/data_saved_sublist.h"
@@ -1090,9 +1091,12 @@ void ComposeControls::initLikeButton() {
}
void ComposeControls::initEditStarsButton() {
if (!_features.editMessageStars) {
if (!editStarsButtonShown()) {
delete base::take(_editStars);
_chosenStarsCount = std::nullopt;
if (_chosenStarsCount) {
_chosenStarsCount = std::nullopt;
updateSendButtonType();
}
return;
}
if (_chosenStarsCount.value_or(0) < _minStarsCount.current()) {
@@ -1128,22 +1132,27 @@ void ComposeControls::editStarsFrom(int selected) {
}));
}
void ComposeControls::updateLikeParent() {
if (_like) {
using namespace Controls;
const auto hidden = _like->isHidden();
const auto &restriction = _writeRestriction.current();
if (_writeRestricted
&& restriction.type == WriteRestrictionType::PremiumRequired) {
_like->setParent(_writeRestricted.get());
void ComposeControls::updateControlsParents() {
const auto toggle = [&](auto &&control, bool inRestriction) {
if (!control) {
return;
}
const auto hidden = control->isHidden();
if (_writeRestricted && inRestriction) {
control->setParent(_writeRestricted.get());
} else {
_like->setParent(_wrap.get());
control->setParent(_wrap.get());
}
if (!hidden) {
_like->show();
control->show();
updateControlsGeometry(_wrap->size());
}
}
};
using Type = Controls::WriteRestrictionType;
const auto &restriction = _writeRestriction.current();
toggle(_like, restriction.type == Type::PremiumRequired);
toggle(_commentsShown, restriction.type != Type::None);
toggle(_starsReaction, restriction.type != Type::None);
}
void ComposeControls::updateFeatures(ChatHelpers::ComposeFeatures features) {
@@ -1156,7 +1165,7 @@ void ComposeControls::updateFeatures(ChatHelpers::ComposeFeatures features) {
} else {
_like = Ui::CreateChild<Ui::IconButton>(_wrap.get(), _st.like);
initLikeButton();
updateLikeParent();
updateControlsParents();
if (updateLikeShown()) {
updateControlsVisibility();
}
@@ -1285,19 +1294,26 @@ void ComposeControls::setToggleCommentsButton(
_commentsShown->setClickedCallback([=] {
_commentsShownToggles.fire({});
});
updateControlsParents();
_commentsShownHidden.value(
) | rpl::start_with_next([=](bool hidden) {
if (_commentsShown->isHidden() != hidden) {
if (hidden) {
_commentsShown->hide();
} else {
_commentsShown->show();
updateControlsGeometry(_wrap->size());
}
}
}, _commentsShown->lifetime());
std::move(
state
) | rpl::start_with_next([=](ToggleCommentsState value) {
if (value == ToggleCommentsState::Empty) {
if (!_commentsShown->isHidden()) {
_commentsShown->hide();
updateControlsGeometry(_wrap->size());
}
_commentsShownHidden = true;
return;
} else if (_commentsShown->isHidden()) {
_commentsShown->show();
updateControlsGeometry(_wrap->size());
}
_commentsShownHidden = false;
const auto icon = (value == ToggleCommentsState::Shown)
? &_st.commentsShown
: nullptr;
@@ -1328,6 +1344,7 @@ void ComposeControls::setStarsReactionCounter(
_st.attach,
_st.starsReactionCounter,
std::move(count));
updateControlsParents();
updateControlsVisibility();
_starsReaction->widthValue(
@@ -1895,7 +1912,7 @@ bool ComposeControls::showRecordButton() const {
}
bool ComposeControls::showEditStarsButton() const {
return _features.editMessageStars
return editStarsButtonShown()
&& !HasSendText(_field)
&& !readyToForward()
&& !isEditingMessage()
@@ -1912,7 +1929,7 @@ void ComposeControls::clearListenState() {
}
void ComposeControls::clearChosenStarsForMessage() {
const auto empty = _features.editMessageStars
const auto empty = editStarsButtonShown()
? _minStarsCount.current()
: std::optional<int>();
if (_chosenStarsCount != empty) {
@@ -1921,6 +1938,10 @@ void ComposeControls::clearChosenStarsForMessage() {
}
}
bool ComposeControls::editStarsButtonShown() const {
return _features.editMessageStars && !_videoStreamAdmin.current();
}
int ComposeControls::chosenStarsForMessage() const {
return _chosenStarsCount.value_or(0);
}
@@ -2817,7 +2838,7 @@ void ComposeControls::initWriteRestriction() {
}, _writeRestricted->lifetime());
_writeRestricted->resize(
_writeRestricted->width(),
st::historyUnblock.height);
_st.send.inner.height);
const auto background = [=](QPainter &p, QRect clip) {
paintBackground(p, _writeRestricted->rect(), clip);
};
@@ -2949,7 +2970,7 @@ void ComposeControls::updateWrappingVisibility() {
_writeRestricted->setVisible(!hidden && restricted);
}
_wrap->setVisible(!hidden && !restricted);
updateLikeParent();
updateControlsParents();
if (!hidden && !restricted) {
updateControlsGeometry(_wrap->size());
_wrap->raise();
@@ -3159,6 +3180,9 @@ void ComposeControls::updateControlsVisibility() {
if (_scheduled) {
_scheduled->setVisible(!isEditingMessage());
}
if (_commentsShown) {
_commentsShown->setVisible(!_commentsShownHidden.current());
}
if (_starsReaction) {
_starsReaction->show();
}
@@ -3238,6 +3262,7 @@ bool ComposeControls::updateSendAsButton(
if (!_sendAs) {
return false;
}
_videoStreamAdmin = false;
_sendAs = nullptr;
return true;
} else if (_sendAs) {
@@ -3247,8 +3272,18 @@ bool ComposeControls::updateSendAsButton(
_sendAs = std::make_unique<Ui::SendAsButton>(_wrap.get(), st.button);
if (videoStream) {
Ui::SetupSendAsButton(_sendAs.get(), st, videoStream, _show);
_videoStreamAdmin = videoStream->sendAsValue(
) | rpl::map([=](not_null<PeerData*> peer) {
return (videoStream->peer() == peer)
|| (videoStream->creator() && peer->isSelf());
}) | rpl::distinct_until_changed(
) | rpl::after_next([=](bool admin) {
initEditStarsButton();
updateControlsGeometry(_wrap->size());
});
} else {
Ui::SetupSendAsButton(_sendAs.get(), st, rpl::single(peer), _show);
_videoStreamAdmin = false;
}
return true;
}
@@ -3286,15 +3321,13 @@ void ComposeControls::paintBackground(QPainter &p, QRect full, QRect clip) {
p.setBrush(_st.bg);
p.setPen(Qt::NoPen);
const auto r = _st.radius;
if (_commentsShown
&& !_commentsShown->isHidden()
&& !_wrap->isHidden()) {
if (_commentsShown && !_commentsShown->isHidden()) {
p.drawRoundedRect(_commentsShown->geometry(), r, r);
full.setLeft(full.left()
+ _commentsShown->width()
+ _st.commentsSkip);
}
if (_starsReaction && !_wrap->isHidden()) {
if (_starsReaction) {
full.setWidth(full.width()
- _starsReaction->width()
- _st.starsSkip);

View File

@@ -308,7 +308,7 @@ private:
void initKeyHandler();
void initLikeButton();
void initEditStarsButton();
void updateLikeParent();
void updateControlsParents();
void updateSubmitSettings();
void updateSendButtonType();
void updateMessagesTTLShown();
@@ -352,7 +352,8 @@ private:
void clearInlineBot();
void inlineBotChanged();
bool hasSilentBroadcastToggle() const;
[[nodiscard]] bool hasSilentBroadcastToggle() const;
[[nodiscard]] bool editStarsButtonShown() const;
// Look in the _field for the inline bot and query string.
void updateInlineBotQuery();
@@ -423,6 +424,7 @@ private:
rpl::variable<int> _minStarsCount;
std::optional<int> _chosenStarsCount;
Ui::IconButton *_commentsShown = nullptr;
rpl::variable<bool> _commentsShownHidden;
Ui::RpWidget *_commentsShownNewDot = nullptr;
Ui::IconButton *_attachToggle = nullptr;
Ui::AbstractButton *_starsReaction = nullptr;
@@ -432,6 +434,7 @@ private:
const not_null<Ui::InputField*> _field;
Ui::IconButton * const _botCommandStart = nullptr;
std::unique_ptr<Ui::SendAsButton> _sendAs;
rpl::variable<bool> _videoStreamAdmin;
std::unique_ptr<Ui::SilentToggle> _silent;
std::unique_ptr<Controls::TTLButton> _ttlInfo;
base::unique_qptr<Controls::CharactersLimitLabel> _charsLimitation;

View File

@@ -73,6 +73,19 @@ namespace {
}));
}
[[nodiscard]] not_null<Ui::RpWidget*> AddMoneyInputIcon(
not_null<QWidget*> parent,
Ui::Text::PaletteDependentEmoji emoji) {
auto helper = Ui::Text::CustomEmojiHelper();
auto text = helper.paletteDependent(std::move(emoji));
return Ui::CreateChild<Ui::FlatLabel>(
parent,
rpl::single(std::move(text)),
st::defaultFlatLabel,
st::defaultPopupMenu,
helper.context());
}
} // namespace
void ChooseSuggestTimeBox(
@@ -138,6 +151,60 @@ void AddApproximateUsd(
usd->widthValue() | rpl::start_with_next(move, usd->lifetime());
}
not_null<Ui::NumberInput*> AddStarsInputField(
not_null<Ui::VerticalLayout*> container,
StarsInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<Ui::FixedHeightWidget>(
container,
st::editTagField.heightMin),
st::boxRowPadding);
const auto result = Ui::CreateChild<Ui::NumberInput>(
wrap,
st::editTagField,
rpl::single(u"0"_q),
args.value ? QString::number(*args.value) : QString(),
args.max ? args.max : std::numeric_limits<int>::max());
const auto icon = AddMoneyInputIcon(
result,
Ui::Earn::IconCreditsEmoji());
wrap->widthValue() | rpl::start_with_next([=](int width) {
icon->move(st::starsFieldIconPosition);
result->move(0, 0);
result->resize(width, result->height());
wrap->resize(width, result->height());
}, wrap->lifetime());
return result;
}
not_null<Ui::InputField*> AddTonInputField(
not_null<Ui::VerticalLayout*> container,
TonInputFieldArgs &&args) {
const auto wrap = container->add(
object_ptr<Ui::FixedHeightWidget>(
container,
st::editTagField.heightMin),
st::boxRowPadding);
const auto result = Ui::CreateTonAmountInput(
wrap,
rpl::single('0' + Ui::TonAmountSeparator() + '0'),
args.value);
const auto icon = AddMoneyInputIcon(
result,
Ui::Earn::IconCurrencyEmoji());
wrap->widthValue() | rpl::start_with_next([=](int width) {
icon->move(st::tonFieldIconPosition);
result->move(0, 0);
result->resize(width, result->height());
wrap->resize(width, result->height());
}, wrap->lifetime());
return result;
}
StarsTonPriceInput AddStarsTonPriceInput(
not_null<Ui::VerticalLayout*> container,
StarsTonPriceArgs &&args) {
@@ -153,18 +220,6 @@ StarsTonPriceInput AddStarsTonPriceInput(
const auto session = args.session;
const auto added = st::boxRowPadding - st::defaultSubsectionTitlePadding;
auto helper = Ui::Text::CustomEmojiHelper();
const auto makeIcon = [&](
not_null<QWidget*> parent,
Ui::Text::PaletteDependentEmoji emoji) {
auto text = helper.paletteDependent(std::move(emoji));
return Ui::CreateChild<Ui::FlatLabel>(
parent,
rpl::single(std::move(text)),
st::defaultFlatLabel,
st::defaultPopupMenu,
helper.context());
};
const auto starsWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
@@ -181,30 +236,11 @@ StarsTonPriceInput AddStarsTonPriceInput(
added.right(),
-st::defaultSubsectionTitlePadding.bottom()));
const auto starsFieldWrap = starsInner->add(
object_ptr<Ui::FixedHeightWidget>(
starsInner,
st::editTagField.heightMin),
st::boxRowPadding);
auto ownedStarsField = object_ptr<Ui::NumberInput>(
starsFieldWrap,
st::editTagField,
rpl::single(u"0"_q),
((args.price && args.price.stars())
? QString::number(args.price.whole())
: QString()),
args.starsMax);
const auto starsField = ownedStarsField.data();
const auto starsIcon = makeIcon(
starsField,
Ui::Earn::IconCreditsEmoji());
starsFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
starsIcon->move(st::starsFieldIconPosition);
starsField->move(0, 0);
starsField->resize(width, starsField->height());
starsFieldWrap->resize(width, starsField->height());
}, starsFieldWrap->lifetime());
const auto starsField = AddStarsInputField(starsInner, {
.value = ((args.price && args.price.stars())
? args.price.whole()
: std::optional<int64>()),
});
AddApproximateUsd(
starsField,
@@ -232,27 +268,11 @@ StarsTonPriceInput AddStarsTonPriceInput(
added.right(),
-st::defaultSubsectionTitlePadding.bottom()));
const auto tonFieldWrap = tonInner->add(
object_ptr<Ui::FixedHeightWidget>(
tonInner,
st::editTagField.heightMin),
st::boxRowPadding);
auto ownedTonField = object_ptr<Ui::InputField>::fromRaw(
Ui::CreateTonAmountInput(
tonFieldWrap,
rpl::single('0' + Ui::TonAmountSeparator() + '0'),
((args.price && args.price.ton())
? (args.price.whole() * Ui::kNanosInOne + args.price.nano())
: 0)));
const auto tonField = ownedTonField.data();
const auto tonIcon = makeIcon(tonField, Ui::Earn::IconCurrencyEmoji());
tonFieldWrap->widthValue() | rpl::start_with_next([=](int width) {
tonIcon->move(st::tonFieldIconPosition);
tonField->move(0, 0);
tonField->resize(width, tonField->height());
tonFieldWrap->resize(width, tonField->height());
}, tonFieldWrap->lifetime());
const auto tonField = AddTonInputField(tonInner, {
.value = (args.price && args.price.ton())
? (args.price.whole() * Ui::kNanosInOne + args.price.nano())
: 0,
});
AddApproximateUsd(
tonField,

View File

@@ -16,6 +16,8 @@ class Show;
namespace Ui {
class GenericBox;
class VerticalLayout;
class NumberInput;
class InputField;
} // namespace Ui
namespace Main {
@@ -44,6 +46,21 @@ void ChooseSuggestTimeBox(
not_null<Ui::GenericBox*> box,
SuggestTimeBoxArgs &&args);
struct StarsInputFieldArgs {
std::optional<int64> value;
int64 max = 0;
};
[[nodiscard]] not_null<Ui::NumberInput*> AddStarsInputField(
not_null<Ui::VerticalLayout*> container,
StarsInputFieldArgs &&args);
struct TonInputFieldArgs {
int64 value = 0;
};
[[nodiscard]] not_null<Ui::InputField*> AddTonInputField(
not_null<Ui::VerticalLayout*> container,
TonInputFieldArgs &&args);
struct StarsTonPriceInput {
Fn<void()> focusCallback;
Fn<std::optional<CreditsAmount>()> computeResult;

View File

@@ -30,6 +30,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
namespace HistoryView::details {
not_null<Main::Session*> SessionFromShow(
const std::shared_ptr<ChatHelpers::Show> &show) {
return &show->session();
}
} // namespace HistoryView::details
namespace HistoryView {
namespace {
@@ -75,7 +84,8 @@ bool CanScheduleUntilOnline(not_null<PeerData*> peer) {
void ScheduleBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Main::Session*> session,
std::shared_ptr<ChatHelpers::Show> maybeShow,
const Api::SendOptions &initialOptions,
const SendMenu::Details &details,
Fn<void(Api::SendOptions)> done,
@@ -114,12 +124,14 @@ void ScheduleBox(
});
if (repeat) {
const auto boxShow = box->uiShow();
const auto showPremiumPromo = [=] {
if (show->session().premium()) {
if (session->premium()) {
return false;
}
Settings::ShowPremiumPromoToast(
show,
Main::MakeSessionShow(boxShow, session),
ChatHelpers::ResolveWindowDefault(),
tr::lng_schedule_repeat_promo(
tr::now,
lt_link,
@@ -131,16 +143,16 @@ void ScheduleBox(
return true;
};
auto locked = Data::AmPremiumValue(
&show->session()
session
) | rpl::map([=](bool premium) {
return !premium;
});
const auto row = box->addRow(Ui::ChooseRepeatPeriod(box, {
.value = show->session().premium() ? *repeat : TimeId(),
.value = session->premium() ? *repeat : TimeId(),
.locked = std::move(locked),
.filter = showPremiumPromo,
.changed = [=](TimeId value) { *repeat = value; },
.test = show->session().isTestMode(),
.test = session->isTestMode(),
}), style::al_top);
std::move(descriptor.width) | rpl::start_with_next([=](int width) {
row->setNaturalWidth(width);
@@ -169,7 +181,7 @@ void ScheduleBox(
});
SetupMenuAndShortcuts(
descriptor.submit.data(),
show,
maybeShow,
[=] { return childDetails; },
sendAction);

View File

@@ -23,6 +23,13 @@ namespace SendMenu {
struct Details;
} // namespace SendMenu
namespace HistoryView::details {
[[nodiscard]] not_null<Main::Session*> SessionFromShow(
const std::shared_ptr<ChatHelpers::Show> &show);
} // namespace HistoryView::details
namespace HistoryView {
[[nodiscard]] TimeId DefaultScheduleTime();
@@ -37,7 +44,8 @@ struct ScheduleBoxStyleArgs {
void ScheduleBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Main::Session*> session,
std::shared_ptr<ChatHelpers::Show> maybeShow,
const Api::SendOptions &initialOptions,
const SendMenu::Details &details,
Fn<void(Api::SendOptions)> done,
@@ -53,8 +61,10 @@ template <typename Guard, typename Submit>
const Api::SendOptions &initialOptions = {},
TimeId scheduleTime = DefaultScheduleTime(),
ScheduleBoxStyleArgs style = ScheduleBoxStyleArgs()) {
const auto session = details::SessionFromShow(show);
return Box(
ScheduleBox,
session,
std::move(show),
initialOptions,
details,
@@ -63,4 +73,24 @@ template <typename Guard, typename Submit>
std::move(style));
}
template <typename Guard, typename Submit>
[[nodiscard]] object_ptr<Ui::GenericBox> PrepareScheduleBox(
Guard &&guard,
not_null<Main::Session*> session,
const SendMenu::Details &details,
Submit &&submit,
const Api::SendOptions &initialOptions = {},
TimeId scheduleTime = DefaultScheduleTime(),
ScheduleBoxStyleArgs style = ScheduleBoxStyleArgs()) {
return Box(
ScheduleBox,
session,
nullptr,
initialOptions,
details,
crl::guard(std::forward<Guard>(guard), std::forward<Submit>(submit)),
scheduleTime,
std::move(style));
}
} // namespace HistoryView

View File

@@ -415,7 +415,7 @@ void SelfForwardsTagger::setupToastTimer(
return base::EventFilterResult::Continue;
}
return base::EventFilterResult::Continue;
}, state->timerLifetime);
}, widget->lifetime());
restartTimer(kInitTimer);
}

View File

@@ -62,7 +62,8 @@ MediaGeneric::MediaGeneric(
Fn<void(std::unique_ptr<Part>)>)> generate,
MediaGenericDescriptor &&descriptor)
: Media(parent)
, _paintBg(std::move(descriptor.paintBg))
, _paintBgFactory(std::move(descriptor.paintBgFactory))
, _paintBg(_paintBgFactory ? _paintBgFactory() : nullptr)
, _fullAreaLink(descriptor.fullAreaLink)
, _maxWidthCap(descriptor.maxWidth)
, _service(descriptor.service)
@@ -110,7 +111,11 @@ void MediaGeneric::draw(Painter &p, const PaintContext &context) const {
const auto outer = width();
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return;
} else if (_paintBg) {
}
if (!_paintBg && _paintBgFactory) {
_paintBg = _paintBgFactory();
}
if (_paintBg) {
_paintBg(p, context, this);
} else if (_service) {
PainterHighQualityEnabler hq(p);
@@ -208,6 +213,7 @@ bool MediaGeneric::hasHeavyPart() const {
}
void MediaGeneric::unloadHeavyPart() {
_paintBg = nullptr;
for (const auto &entry : _entries) {
entry.object->unloadHeavyPart();
}

View File

@@ -29,6 +29,12 @@ class MediaGeneric;
class MediaGenericPart : public Object {
public:
using PaintBg = Fn<void(
Painter&,
const PaintContext&,
not_null<const MediaGeneric*>)>;
using PaintBgFactory = Fn<PaintBg()>;
virtual ~MediaGenericPart() = default;
virtual void draw(
@@ -53,10 +59,7 @@ public:
struct MediaGenericDescriptor {
int maxWidth = 0;
Fn<void(
Painter&,
const PaintContext&,
not_null<const MediaGeneric*>)> paintBg;
MediaGenericPart::PaintBgFactory paintBgFactory;
ClickHandlerPtr fullAreaLink;
bool service = false;
bool hideServiceText = false;
@@ -124,10 +127,8 @@ private:
[[nodiscard]] QMargins inBubblePadding() const;
std::vector<Entry> _entries;
Fn<void(
Painter&,
const PaintContext&,
not_null<const MediaGeneric*>)> _paintBg;
Part::PaintBgFactory _paintBgFactory;
mutable Part::PaintBg _paintBg;
ClickHandlerPtr _fullAreaLink;
int _maxWidthCap = 0;
int _marginTop = 0;

View File

@@ -69,111 +69,114 @@ QSize PremiumGift::size() {
}
TextWithEntities PremiumGift::title() {
using namespace Ui::Text;
if (tonGift()) {
return tr::lng_gift_ton_amount(
tr::now,
lt_count_decimal,
CreditsAmount(0, _data.count, CreditsType::Ton).value(),
Ui::Text::WithEntities);
tr::marked);
} else if (starGift()) {
const auto peer = _parent->history()->peer;
const auto to = _data.auctionTo ? _data.auctionTo : peer.get();
return peer->isSelf()
? tr::lng_action_gift_self_subtitle(tr::now, WithEntities)
? tr::lng_action_gift_self_subtitle(tr::now, tr::marked)
: (peer->isServiceUser() && _data.channelFrom)
? tr::lng_action_gift_got_subtitle(
tr::now,
lt_user,
WithEntities({})
.append(SingleCustomEmoji(
tr::marked()
.append(Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager(
).peerUserpicEmojiData(_data.channelFrom)))
).peerUserpicEmojiData(_data.channelFrom)))
.append(' ')
.append(_data.channelFrom->shortName()),
WithEntities)
: peer->isServiceUser()
? tr::lng_gift_link_label_gift(tr::now, WithEntities)
tr::marked)
: (!_data.auctionTo && peer->isServiceUser())
? tr::lng_gift_link_label_gift(tr::now, tr::marked)
: (outgoingGift()
? tr::lng_action_gift_sent_subtitle
: tr::lng_action_gift_got_subtitle)(
tr::now,
lt_user,
WithEntities({})
.append(SingleCustomEmoji(
peer->owner().customEmojiManager(
).peerUserpicEmojiData(peer)))
tr::marked()
.append(Ui::Text::SingleCustomEmoji(
to->owner().customEmojiManager(
).peerUserpicEmojiData(to)))
.append(' ')
.append(peer->shortName()),
WithEntities);
.append(to->shortName()),
tr::marked);
} else if (creditsPrize()) {
return tr::lng_prize_title(tr::now, WithEntities);
} else if (const auto c = credits()) {
return tr::lng_gift_stars_title(tr::now, lt_count, c, WithEntities);
return tr::lng_prize_title(tr::now, tr::marked);
} else if (const auto stars = credits()) {
return tr::lng_gift_stars_title(tr::now, lt_count_decimal, stars, tr::marked);
}
return gift()
? tr::lng_action_gift_premium_months(
tr::now,
lt_count,
_data.count,
WithEntities)
premiumMonths(),
tr::marked)
: _data.unclaimed
? tr::lng_prize_unclaimed_title(tr::now, WithEntities)
: tr::lng_prize_title(tr::now, WithEntities);
? tr::lng_prize_unclaimed_title(tr::now, tr::marked)
: tr::lng_prize_title(tr::now, tr::marked);
}
TextWithEntities PremiumGift::author() {
using namespace Ui::Text;
if (!_data.stargiftReleasedBy) {
return {};
}
return tr::lng_gift_released_by(
tr::now,
lt_name,
Ui::Text::Link('@' + _data.stargiftReleasedBy->username()),
Ui::Text::WithEntities);
tr::link('@' + _data.stargiftReleasedBy->username()),
tr::marked);
}
TextWithEntities PremiumGift::subtitle() {
if (tonGift()) {
return tr::lng_action_gift_got_ton(tr::now, Ui::Text::WithEntities);
return tr::lng_action_gift_got_ton(tr::now, tr::marked);
} else if (starGift()) {
const auto toChannel = _data.channel
&& _parent->history()->peer->isServiceUser();
return !_data.message.empty()
? _data.message
: _data.refunded
? tr::lng_action_gift_refunded(tr::now, Ui::Text::RichLangValue)
? tr::lng_action_gift_refunded(tr::now, tr::rich)
: outgoingGift()
? (_data.starsUpgradedBySender
? (_data.auctionTo
? tr::lng_action_gift_self_auction(
tr::now,
lt_cost,
tr::lng_action_gift_for_stars(
tr::now,
lt_count_decimal,
_data.starsBid,
tr::marked),
tr::rich)
: _data.starsUpgradedBySender
? tr::lng_action_gift_sent_upgradable(
tr::now,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue)
tr::bold(_parent->history()->peer->shortName()),
tr::rich)
: tr::lng_action_gift_sent_text(
tr::now,
lt_count,
lt_count_decimal,
_data.starsConverted,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue))
tr::bold(_parent->history()->peer->shortName()),
tr::rich))
: _data.starsUpgradedBySender
? tr::lng_action_gift_got_upgradable_text(
tr::now,
Ui::Text::RichLangValue)
? tr::lng_action_gift_got_upgradable_text(tr::now, tr::rich)
: (_data.starsToUpgrade
&& !_data.converted
&& _parent->history()->peer->isSelf())
? tr::lng_action_gift_self_about_unique(
tr::now,
Ui::Text::RichLangValue)
? tr::lng_action_gift_self_about_unique(tr::now, tr::rich)
: (_data.starsToUpgrade
&& !_data.converted
&& _parent->history()->peer->isServiceUser()
&& _data.channel)
? tr::lng_action_gift_channel_about_unique(
tr::now,
Ui::Text::RichLangValue)
? tr::lng_action_gift_channel_about_unique(tr::now, tr::rich)
: (!_data.converted && !_data.starsConverted)
? (_data.saved
? (toChannel
@@ -181,9 +184,7 @@ TextWithEntities PremiumGift::subtitle() {
: tr::lng_action_gift_can_remove_text)
: (toChannel
? tr::lng_action_gift_got_gift_channel
: tr::lng_action_gift_got_gift_text))(
tr::now,
Ui::Text::RichLangValue)
: tr::lng_action_gift_got_gift_text))(tr::now, tr::rich)
: (_data.converted
? (toChannel
? tr::lng_gift_channel_got
@@ -196,7 +197,7 @@ TextWithEntities PremiumGift::subtitle() {
tr::now,
lt_count,
_data.starsConverted,
Ui::Text::RichLangValue);
tr::rich);
}
const auto isCreditsPrize = creditsPrize();
if (const auto count = credits(); count && !isCreditsPrize) {
@@ -204,15 +205,13 @@ TextWithEntities PremiumGift::subtitle() {
? tr::lng_gift_stars_outgoing(
tr::now,
lt_user,
Ui::Text::Bold(_parent->history()->peer->shortName()),
Ui::Text::RichLangValue)
: tr::lng_gift_stars_incoming(tr::now, Ui::Text::WithEntities);
tr::bold(_parent->history()->peer->shortName()),
tr::rich)
: tr::lng_gift_stars_incoming(tr::now, tr::marked);
} else if (gift()) {
return !_data.message.empty()
? _data.message
: tr::lng_action_gift_premium_about(
tr::now,
Ui::Text::RichLangValue);
: tr::lng_action_gift_premium_about(tr::now, tr::rich);
}
const auto name = _data.channel ? _data.channel->name() : "channel";
auto result = (_data.unclaimed
@@ -222,8 +221,8 @@ TextWithEntities PremiumGift::subtitle() {
: tr::lng_prize_gift_about)(
tr::now,
lt_channel,
Ui::Text::Bold(name),
Ui::Text::RichLangValue);
tr::bold(name),
tr::rich);
result.append("\n\n");
result.append(isCreditsPrize
? tr::lng_prize_credits(
@@ -231,10 +230,10 @@ TextWithEntities PremiumGift::subtitle() {
lt_amount,
tr::lng_prize_credits_amount(
tr::now,
lt_count,
lt_count_decimal,
credits(),
Ui::Text::RichLangValue),
Ui::Text::RichLangValue)
tr::marked),
tr::rich)
: (_data.unclaimed
? tr::lng_prize_unclaimed_duration
: _data.viaGiveaway
@@ -242,8 +241,8 @@ TextWithEntities PremiumGift::subtitle() {
: tr::lng_prize_gift_duration)(
tr::now,
lt_duration,
Ui::Text::Bold(GiftDuration(_data.count)),
Ui::Text::RichLangValue));
tr::bold(GiftDuration(premiumDays())),
tr::rich));
return result;
}
@@ -423,12 +422,12 @@ void PremiumGift::unloadHeavyPart() {
bool PremiumGift::incomingGift() const {
const auto out = _parent->data()->out();
return gift() && (starGiftUpgrade() ? out : !out);
return gift() && !_data.auctionTo && (starGiftUpgrade() ? out : !out);
}
bool PremiumGift::outgoingGift() const {
const auto out = _parent->data()->out();
return gift() && (starGiftUpgrade() ? !out : out);
return gift() && (_data.auctionTo || (starGiftUpgrade() ? !out : out));
}
bool PremiumGift::gift() const {
@@ -457,6 +456,14 @@ int PremiumGift::credits() const {
return (_data.type == Data::GiftType::Credits) ? _data.count : 0;
}
int PremiumGift::premiumDays() const {
return (_data.type == Data::GiftType::Premium) ? _data.count : 0;
}
int PremiumGift::premiumMonths() const {
return premiumDays() / 30;
}
void PremiumGift::ensureStickerCreated() const {
if (_sticker) {
return;
@@ -485,7 +492,9 @@ void PremiumGift::ensureStickerCreated() const {
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
const auto count = credits();
const auto months = count ? packs.monthsForStars(count) : _data.count;
const auto months = count
? packs.monthsForStars(count)
: premiumMonths();
if (const auto document = packs.lookup(months)) {
if (document->sticker()) {
const auto skipPremiumEffect = false;

View File

@@ -60,6 +60,8 @@ private:
[[nodiscard]] bool gift() const;
[[nodiscard]] bool creditsPrize() const;
[[nodiscard]] int credits() const;
[[nodiscard]] int premiumDays() const;
[[nodiscard]] int premiumMonths() const;
void ensureStickerCreated() const;
const not_null<Element*> _parent;

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_unique_gift.h"
#include "base/unixtime.h"
#include "boxes/star_gift_box.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/click_handler_types.h"
@@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
#include "data/data_web_page.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_premium_gift.h"
#include "history/view/history_view_cursor_state.h"
@@ -26,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "settings/settings_credits_graphics.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/ministar_particles.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/ripple_animation.h"
#include "ui/layers/generic_box.h"
@@ -536,6 +539,187 @@ auto GenerateUniqueGiftPreview(
};
}
auto GenerateAuctionPreview(
not_null<Element*> parent,
Element *replacing,
std::shared_ptr<Data::StarGift> gift,
Data::UniqueGiftBackdrop backdrop,
TimeId endDate)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto sticker = [=] {
using Tag = ChatHelpers::StickerLottieSize;
return StickerInBubblePart::Data{
.sticker = gift->document,
.size = st::chatIntroStickerSize,
.cacheTag = Tag::ChatIntroHelloSticker,
};
};
push(std::make_unique<StickerInBubblePart>(
parent,
replacing,
sticker,
st::webPageAuctionPreviewPadding));
const auto name = gift->unique
? Data::UniqueGiftName(*gift->unique)
: gift->resellTitle;
if (!name.isEmpty()) {
push(std::make_unique<TextPartColored>(
Ui::Text::Bold(name),
QMargins(0, 0, 0, st::defaultVerticalListSkip),
[c = backdrop.textColor](const auto&) { return c; },
st::chatUniqueTitle));
}
if (const auto all = gift->limitedCount) {
push(std::make_unique<TextPartColored>(
tr::lng_boosts_list_tab_gifts(
tr::now,
lt_count,
all,
Ui::Text::WithEntities),
QMargins(0, 0, 0, st::webPageAuctionPreviewPadding.top()),
[c = backdrop.textColor](const auto&) { return c; },
st::chatUniqueTextStyle));
}
};
}
auto AuctionBg(
not_null<Element*> view,
Data::UniqueGiftBackdrop backdrop,
std::shared_ptr<Data::StarGift> gift,
TimeId endDate)
-> Fn<void(
Painter&,
const Ui::ChatPaintContext&,
not_null<const MediaGeneric*>)> {
struct State {
std::unique_ptr<Ui::Text::CustomEmoji> pattern;
base::flat_map<float64, QImage> cache;
std::optional<Ui::StarParticles> particles;
std::unique_ptr<base::Timer> timer;
crl::time pausedAt = 0;
crl::time pauseOffset = 0;
};
const auto state = std::make_shared<State>();
if (gift->unique && gift->unique->pattern.document) {
state->pattern = view->history()->owner().customEmojiManager().create(
gift->unique->pattern.document,
[=] { view->repaint(); },
Data::CustomEmojiSizeTag::Large);
}
state->particles.emplace(
Ui::StarParticles::Type::RadialInside,
25,
st::lineWidth * 8);
state->particles->setSpeed(0.05);
state->particles->setColor(backdrop.textColor);
return [=](
Painter &p,
const Ui::ChatPaintContext &context,
not_null<const MediaGeneric*> media) {
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
const auto webpreview = (media.get() != view->media());
const auto radius = webpreview
? st::roundRadiusLarge
: st::msgServiceGiftBoxRadius;
const auto full = QRect(0, 0, media->width(), media->height());
auto gradient = QRadialGradient(full.center(), full.height() / 2);
gradient.setStops({
{ 0., backdrop.centerColor },
{ 1., backdrop.edgeColor },
});
p.setBrush(gradient);
p.drawRoundedRect(full, radius, radius);
/*if (state->pattern) {
const auto width = media->width();
const auto shift = width / 12;
const auto doubled = width + 2 * shift;
const auto top = (webpreview ? 2 : 1) * (-shift);
const auto outer = QRect(-shift, top, doubled, doubled);
p.setClipRect(full);
if (gift->unique) {
Ui::PaintBgPoints(
p,
Ui::PatternBgPoints(),
state->cache,
state->pattern.get(),
*gift->unique,
outer);
}
p.setClipping(false);
}*/
if (state->particles) {
p.setClipRect(full);
if (context.paused) {
if (!state->pausedAt) {
state->pausedAt = crl::now();
}
const auto diff = state->pausedAt - state->pauseOffset;
state->particles->paint(p, full, diff);
} else {
if (state->pausedAt) {
state->pauseOffset += crl::now() - state->pausedAt;
state->pausedAt = 0;
}
const auto diff = context.now - state->pauseOffset;
state->particles->paint(p, full, diff);
}
p.setClipping(false);
}
const auto now = base::unixtime::now();
const auto left = std::max(endDate - now, 0);
if (left > 0) {
if (!state->timer) {
state->timer = std::make_unique<base::Timer>([=] {
view->repaint();
});
}
state->timer->callOnce(1000);
} else if (left <= 0 && state->timer) {
state->timer = nullptr;
}
const auto text = left > 0
? QString("%1:%2:%3")
.arg(left / 3600, 2, 10, QChar('0'))
.arg((left % 3600) / 60, 2, 10, QChar('0'))
.arg(left % 60, 2, 10, QChar('0'))
: tr::lng_auctino_preview_finished(tr::now);
const auto &font = st::webPageAuctionTimeFont;
const auto textWidth = font->width(text);
const auto padding = st::webPageAuctionTimerPadding;
const auto timerWidth = textWidth + rect::m::sum::h(padding);
const auto timerHeight = font->height + rect::m::sum::v(padding);
const auto timerRadius = timerHeight / 2.;
const auto timerRect = QRectF(
padding.top(),
padding.top(),
timerWidth,
timerHeight);
p.setPen(Qt::NoPen);
p.setBrush(st::slideFadeOutBg);
p.drawRoundedRect(timerRect, timerRadius, timerRadius);
p.setPen(backdrop.textColor);
p.setFont(font);
p.drawText(
timerRect.x() + padding.left(),
timerRect.y() + padding.top() + font->ascent,
text);
};
}
std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,
QMargins margins,

View File

@@ -14,6 +14,8 @@ class Painter;
namespace Data {
class MediaGiftBox;
struct UniqueGift;
struct UniqueGiftBackdrop;
struct StarGift;
class Birthday;
} // namespace Data
@@ -51,6 +53,26 @@ class MediaGenericPart;
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] auto GenerateAuctionPreview(
not_null<Element*> parent,
Element *replacing,
std::shared_ptr<Data::StarGift> gift,
Data::UniqueGiftBackdrop backdrop,
TimeId endDate)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] auto AuctionBg(
not_null<Element*> view,
Data::UniqueGiftBackdrop backdrop,
std::shared_ptr<Data::StarGift> gift,
TimeId endDate)
-> Fn<void(
Painter&,
const Ui::ChatPaintContext&,
not_null<const MediaGeneric*>)>;
[[nodiscard]] std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,
QMargins margins,

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_web_page.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "countries/countries_instance.h"
#include "base/qt/qt_key_modifiers.h"
@@ -236,6 +237,11 @@ constexpr auto kSponsoredUserpicLines = 2;
? tr::lng_view_button_storyalbum(tr::now)
: (type == WebPageType::GiftCollection)
? tr::lng_view_button_collection(tr::now)
: (type == WebPageType::Auction)
? (page->auction && page->auction->endDate
&& page->auction->endDate <= base::unixtime::now())
? tr::lng_auction_preview_view_results(tr::now)
: tr::lng_auction_preview_join(tr::now)
: QString());
if (page->iv) {
return Ui::Text::IconEmoji(&st::historyIvIcon).append(text);
@@ -271,7 +277,8 @@ constexpr auto kSponsoredUserpicLines = 2;
&& webpage->document->isWallPaper())
|| (type == WebPageType::StickerSet)
|| (type == WebPageType::StoryAlbum)
|| (type == WebPageType::GiftCollection);
|| (type == WebPageType::GiftCollection)
|| (type == WebPageType::Auction);
}
} // namespace
@@ -517,8 +524,36 @@ QSize WebPage::countOptimalSize() {
_data->uniqueGift),
MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftPreview,
.paintBg = UniqueGiftBg(_parent, _data->uniqueGift),
.paintBgFactory = [=] {
return UniqueGiftBg(_parent, _data->uniqueGift);
},
});
} else if (!_attach && _data->auction) {
const auto &gift = _data->auction->auctionGift;
const auto backdrop = Data::UniqueGiftBackdrop{
.centerColor = _data->auction->centerColor,
.edgeColor = _data->auction->edgeColor,
.patternColor = _data->auction->edgeColor,
.textColor = _data->auction->textColor,
};
_attach = std::make_unique<MediaGeneric>(
_parent,
GenerateAuctionPreview(
_parent,
nullptr,
gift,
backdrop,
_data->auction->endDate),
MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftPreview,
.paintBgFactory = [=] {
return AuctionBg(
_parent,
backdrop,
gift,
_data->auction->endDate);
},
});
} else if (!_attach && !_asArticle) {
_attach = CreateAttach(
_parent,
@@ -533,7 +568,8 @@ QSize WebPage::countOptimalSize() {
// init strings
if (_description.isEmpty()
&& !_data->description.text.isEmpty()
&& !_data->uniqueGift) {
&& !_data->uniqueGift
&& !_data->auction) {
const auto &text = _data->description;
using Type = Core::TextContextDetails::HashtagMentionType;
auto context = Core::TextContext({

View File

@@ -40,6 +40,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QCoreApplication>
namespace Info {
namespace {
class FlexibleFiller final : public Ui::RpWidget {
public:
using RpWidget::RpWidget;
void setTargetWidget(base::unique_qptr<RpWidget> widget);
private:
void visibleTopBottomUpdated(int visibleTop, int visibleBottom) override;
base::unique_qptr<RpWidget> _target;
};
void FlexibleFiller::setTargetWidget(base::unique_qptr<RpWidget> widget) {
Expects(!_target);
_target = std::move(widget);
}
void FlexibleFiller::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
if (const auto raw = _target.get()) {
raw->setVisibleTopBottom(visibleTop, visibleBottom);
}
}
} // namespace
ContentWidget::ContentWidget(
QWidget *parent,
@@ -203,6 +233,36 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
return _innerWrap->entity();
}
Ui::RpWidget *ContentWidget::doSetupFlexibleInnerWidget(
object_ptr<Ui::RpWidget> inner,
FlexibleScrollData &flexibleScroll,
Fn<void(Ui::RpWidget*)> customSetup) {
const auto filler = setInnerWidget(object_ptr<FlexibleFiller>(this));
filler->resize(1, 1);
flexibleScroll.contentHeightValue.events(
) | rpl::start_with_next([=](int h) {
filler->resize(filler->width(), h);
}, filler->lifetime());
filler->widthValue(
) | rpl::start_to_stream(
flexibleScroll.fillerWidthValue,
filler->lifetime());
if (customSetup) {
customSetup(filler);
}
// ScrollArea -> PaddingWrap -> RpWidget.
const auto result = inner.release();
result->setParent(filler->parentWidget()->parentWidget());
result->raise();
filler->setTargetWidget(base::unique_qptr<Ui::RpWidget>(result));
return result;
}
int ContentWidget::scrollTillBottom(int forHeight) const {
const auto scrollHeight = forHeight
- _scrollTopSkip.current()
@@ -384,7 +444,7 @@ void ContentWidget::refreshSearchField(bool shown) {
view->show();
_searchField->setFocus();
setScrollTopSkip(view->heightNoMargins() - st::lineWidth);
} else {
} else if (_searchWrap) {
if (Ui::InFocusChain(this)) {
setFocus();
}

View File

@@ -77,6 +77,7 @@ namespace Info {
class ContentMemento;
class Controller;
struct FlexibleScrollData;
class ContentWidget : public Ui::RpWidget {
public:
@@ -155,40 +156,18 @@ protected:
doSetInnerWidget(std::move(inner)));
}
template <typename Widget, typename FlexibleData>
template <typename Widget>
Widget *setupFlexibleInnerWidget(
object_ptr<Widget> inner,
FlexibleData &flexibleScroll,
FlexibleScrollData &flexibleScroll,
Fn<void(Ui::RpWidget*)> customSetup = nullptr) {
if (inner->hasFlexibleTopBar()) {
auto filler = setInnerWidget(object_ptr<Ui::RpWidget>(this));
filler->resize(1, 1);
flexibleScroll.contentHeightValue.events(
) | rpl::start_with_next([=](int h) {
filler->resize(filler->width(), h);
}, filler->lifetime());
filler->widthValue(
) | rpl::start_to_stream(
flexibleScroll.fillerWidthValue,
lifetime());
if (customSetup) {
customSetup(filler);
}
// ScrollArea -> PaddingWrap -> RpWidget.
inner->setParent(filler->parentWidget()->parentWidget());
inner->raise();
using InnerPtr = base::unique_qptr<Widget>;
auto owner = filler->lifetime().make_state<InnerPtr>(
std::move(inner.release()));
return owner->get();
} else {
if (!inner->hasFlexibleTopBar()) {
return setInnerWidget(std::move(inner));
}
return static_cast<Widget*>(doSetupFlexibleInnerWidget(
std::move(inner),
flexibleScroll,
std::move(customSetup)));
}
[[nodiscard]] not_null<Controller*> controller() const {
@@ -214,7 +193,12 @@ protected:
void setViewport(rpl::producer<not_null<QEvent*>> &&events) const;
private:
RpWidget *doSetInnerWidget(object_ptr<RpWidget> inner);
Ui::RpWidget *doSetInnerWidget(object_ptr<Ui::RpWidget> inner);
Ui::RpWidget *doSetupFlexibleInnerWidget(
object_ptr<Ui::RpWidget> inner,
FlexibleScrollData &flexibleScroll,
Fn<void(Ui::RpWidget*)> customSetup);
void updateControlsGeometry();
void refreshSearchField(bool shown);
void setupSwipeHandler(not_null<Ui::RpWidget*> widget);

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/info_flexible_scroll.h"
#include "ui/effects/animation_value.h"
#include "ui/widgets/scroll_area.h"
#include "base/event_filter.h"
#include "base/options.h"
@@ -65,7 +66,7 @@ void FlexibleScrollHelper::setupScrollAnimation() {
_scrollTopFrom,
_scrollTopTo,
std::clamp(eased, 0., 1.));
_scroll->scrollToY(scrollCurrent);
scrollToY(scrollCurrent);
_lastScrollApplied = scrollCurrent;
if (progress >= 1) {
clearScrollState();
@@ -121,7 +122,7 @@ void FlexibleScrollHelper::setupScrollHandling() {
: -1);
{
_applyingFakeScrollState = true;
_scroll->scrollToY(previousValue);
scrollToY(previousValue);
_applyingFakeScrollState = false;
}
if (_scrollAnimation.animating()
@@ -209,16 +210,21 @@ void FlexibleScrollHelper::setupScrollHandling() {
}
void FlexibleScrollHelper::setupScrollHandlingWithFilter() {
const auto heightDiff = [=] {
return _pinnedToTop->maximumHeight()
- _pinnedToTop->minimumHeight();
};
rpl::combine(
_pinnedToTop->heightValue(),
_inner->heightValue()
) | rpl::start_with_next([=](int, int h) {
_data.contentHeightValue.fire(h + heightDiff());
const auto max = _pinnedToTop->maximumHeight();
const auto min = _pinnedToTop->minimumHeight();
const auto diff = max - min;
const auto progress = (diff > 0)
? std::clamp(
(_pinnedToTop->height() - min) / float64(diff),
0.,
1.)
: 1.;
_data.contentHeightValue.fire(h
+ anim::interpolate(diff, 0, progress));
}, _pinnedToTop->lifetime());
const auto singleStep = _scroll->verticalScrollBar()->singleStep()
@@ -237,7 +243,8 @@ void FlexibleScrollHelper::setupScrollHandlingWithFilter() {
const auto wheel = static_cast<QWheelEvent*>(e.get());
const auto delta = wheel->angleDelta().y();
if (std::abs(delta) != 120) {
return base::EventFilterResult::Continue;
scrollToY(_scroll->scrollTop() - delta);
return base::EventFilterResult::Cancel;
}
const auto actualTop = _scroll->scrollTop();
const auto animationActive = _scrollAnimation.animating()
@@ -318,13 +325,8 @@ void FlexibleScrollHelper::setupScrollHandlingWithFilter() {
return base::EventFilterResult::Cancel;
}, _filterLifetime);
_scroll->scrollTopValue(
) | rpl::start_with_next([=](int top) {
const auto current = heightDiff() - top;
_inner->moveToLeft(0, std::min(0, current));
_pinnedToTop->resize(
_pinnedToTop->width(),
std::max(current + _pinnedToTop->minimumHeight(), 0));
_scroll->scrollTopValue() | rpl::start_with_next([=](int top) {
applyScrollToPinnedLayout(top);
}, _inner->lifetime());
_data.fillerWidthValue.events(
@@ -339,4 +341,21 @@ void FlexibleScrollHelper::setupScrollHandlingWithFilter() {
}));
}
} // namespace Info
void FlexibleScrollHelper::scrollToY(int scrollCurrent) {
applyScrollToPinnedLayout(scrollCurrent);
_scroll->scrollToY(scrollCurrent);
}
void FlexibleScrollHelper::applyScrollToPinnedLayout(int scrollCurrent) {
const auto top = std::min(scrollCurrent, _scroll->scrollTopMax());
const auto minimumHeight = _pinnedToTop->minimumHeight();
const auto current = _pinnedToTop->maximumHeight()
- minimumHeight
- top;
_inner->moveToLeft(0, std::min(0, current));
_pinnedToTop->resize(
_pinnedToTop->width(),
std::max(current + minimumHeight, 0));
}
} // namespace Info

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