Compare commits

...

139 Commits

Author SHA1 Message Date
John Preston
6e75a41ee6 Version 6.3.4: Fix build with Xcode. 2025-11-26 16:44:09 +04:00
John Preston
84266aef2c Version 6.3.4.
- Show active auctions above chats list.
- Add star sending effects in live stories.
- Fix back button in group member profile view.
- Export separate topics message history.
- Export audio files saved to profile.
- Many different crash fixes.
2025-11-26 14:58:32 +04:00
John Preston
40a7f8ea50 Try allowing only tab-focus for screen readers. 2025-11-26 14:57:35 +04:00
John Preston
7c4fcdd9cb Update emoji to Unicode 16. 2025-11-26 14:48:16 +04:00
John Preston
92e87852c1 Fix top bar title/subtitle in saved messages.
Fixes #30018.
2025-11-26 13:27:32 +04:00
John Preston
73c4da2b21 Show '+' to the right of all bids. 2025-11-26 13:01:15 +04:00
23rd
9f4da7e890 Added button for profile color to settings information section. 2025-11-26 05:37:09 +03:00
23rd
8b23457373 Fixed display of verified icon in box from web bot preview.
Regression was introduced in e7fa330215.
2025-11-26 05:30:38 +03:00
23rd
ab2fd7c749 Once again fixed position of tag preview in chats filter settings.
Previous related commit 512e6de39b.
2025-11-26 05:20:23 +03:00
John Preston
040a6ddf3a Fix build with MSVC. 2025-11-25 21:34:26 +04:00
John Preston
55afe0912f Add screen reader state logging. 2025-11-25 21:28:57 +04:00
23rd
ee48127094 Added send small button to box of entry from credits earn history. 2025-11-25 20:01:09 +03:00
23rd
445576d568 Added ToS link to any box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
8f1d40892e Added info of limited gifts to box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
766db9660c Swap name of sender and subtext for paid messages in earn history list. 2025-11-25 18:59:47 +03:00
23rd
68665ec1f2 Added phrase for paid reactions of live stories to credits earn history. 2025-11-25 18:59:47 +03:00
John Preston
b400964aa1 Don't allow admin to send stars in streams. 2025-11-25 19:37:26 +04:00
John Preston
317530cfa3 Attempt to fix audio cracks in streams. 2025-11-25 19:37:10 +04:00
John Preston
e8fba23b59 Don't allow sending stars from stream admin. 2025-11-25 19:05:55 +04:00
John Preston
ef749e695e Show nice layout of stars-only messages. 2025-11-25 18:30:09 +04:00
John Preston
712ef33d6b Submit custom bid with Enter. 2025-11-25 17:50:25 +04:00
John Preston
0441b7dbc3 Show star senders in live stories. 2025-11-25 17:50:25 +04:00
23rd
3ee0dcbacd Provided topic name to export controller for topic history. 2025-11-25 06:19:41 +03:00
23rd
57411b962f Added initial ability to export topic history. 2025-11-25 06:19:34 +03:00
23rd
4eee00d95e Added forwarded_from_id field to json export for forwarded messages. 2025-11-25 06:14:06 +03:00
23rd
957a08962f Added dark theme to exported html. 2025-11-25 06:14:06 +03:00
23rd
21c82f5fe1 Added ability to view stories anonymously from context menu. 2025-11-25 06:14:06 +03:00
23rd
27964993f6 Added safe guard to userpic in peer qr box. 2025-11-25 06:14:06 +03:00
John Preston
14e296e1f9 Improve layout of about auction box. 2025-11-24 22:06:55 +04:00
John Preston
23c0ff934f Skip premium badge for Saved Messages. 2025-11-24 22:01:56 +04:00
John Preston
c64ef1e20e Allow transfering gifts without premium. 2025-11-24 21:04:51 +04:00
John Preston
ed97619c6c Guard by widget instead of session. 2025-11-24 19:22:20 +04:00
23rd
924ec592b1 Improved paint with ready full peer before animation in profile top bar. 2025-11-24 18:18:13 +03:00
23rd
669c581701 Added dummy filler to PeerQrBox when result link is empty. 2025-11-24 17:46:49 +03:00
23rd
e97ae3d537 Fixed display of QR button in info profile when no usernames. 2025-11-24 17:46:49 +03:00
23rd
08800b68f4 Slightly improved subtext for topic link in info profile. 2025-11-24 17:46:49 +03:00
23rd
a59db6529c Added default guarded callback for simple QR box for peer username. 2025-11-24 17:46:49 +03:00
dependabot[bot]
02a54ceea6 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 17:59:24 +04:00
John Preston
f0a7c547e8 Add safe check for strange click reports. 2025-11-24 17:51:42 +04:00
John Preston
ecfb343690 Fix crash in going from saved music to a chat. 2025-11-24 17:38:54 +04:00
John Preston
c65472c9b3 Add more assertions to find a problem. 2025-11-24 17:27:29 +04:00
John Preston
c42864d35e Fix crash in several video removed from video call. 2025-11-24 17:08:10 +04:00
John Preston
b02ce599e6 Fix possible crash in usernames order saving. 2025-11-24 14:47:14 +04:00
John Preston
1ae5495b91 Add some more assertions for subsection tabs. 2025-11-24 14:20:10 +04:00
John Preston
c4fbb8c199 Fix possible crash in group call box closing. 2025-11-24 14:19:37 +04:00
John Preston
313872dacc Fix crash in live location message destruction. 2025-11-24 12:23:13 +04:00
John Preston
ef15136a3b Fix possible crash in conference box closing. 2025-11-24 10:12:38 +04:00
John Preston
4342c8d761 Fix Live stories display without OpenGL. 2025-11-24 09:59:34 +04:00
23rd
644744ac9e Improved phrase of original date in self. 2025-11-23 18:29:01 +03:00
23rd
cbc03d1e45 Fixed display of original date in self when original sender is hidden. 2025-11-23 18:13:34 +03:00
23rd
7f56192b97 Fixed handler of login email in intro section.
This occurs both log in with setting up of login email
and log in by code from login email (set before).
2025-11-23 16:25:01 +03:00
23rd
6590f3b741 Added simple handle of EMAIL_NOT_SETUP error from email login section. 2025-11-23 16:25:01 +03:00
23rd
c0bbb669e0 Fixed unwanted switching to default filter on creation of tabs strip. 2025-11-23 16:25:01 +03:00
23rd
63014adfef Fixed rpl trigger for back button in profile top bar. 2025-11-23 16:25:01 +03:00
23rd
1ad055c8c8 Updated libiconv to v1.18. 2025-11-22 12:36:36 +03:00
John Preston
9b558564e9 Show my place correctly. 2025-11-21 21:26:44 +04:00
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
231 changed files with 6243 additions and 1377 deletions

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -59,7 +59,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -40,7 +40,7 @@ jobs:
macos:
name: MacOS
runs-on: macos-15-intel
runs-on: macos-latest
strategy:
matrix:
@@ -56,7 +56,7 @@ jobs:
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: ${{ env.REPO_NAME }}

View File

@@ -60,7 +60,7 @@ jobs:
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: ${{ env.REPO_NAME }}

View File

@@ -47,7 +47,7 @@ jobs:
steps:
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
@@ -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

@@ -72,7 +72,7 @@ jobs:
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 KiB

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 250 KiB

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 {
@@ -633,4 +652,101 @@ div.toast_shown {
.reactions .reaction .count {
margin-right: 8px;
line-height: 20px;
}
@media (prefers-color-scheme: dark) {
html, body {
background-color: #1a2026; /* groupCallBg */
margin: 0;
padding: 0;
}
.page_wrap {
background-color: #1a2026; /* groupCallBg */
color: #ffffff; /* groupCallMembersFg */
min-height: 100vh;
}
.page_wrap a {
color: #4db8ff; /* groupCallActiveFg */
}
.page_header {
background-color: #1a2026; /* groupCallBg */
border-bottom: 1px solid #2c333d; /* groupCallMembersBg */
}
.bold {
color: #ffffff; /* groupCallMembersFg */
}
.details {
color: #91979e; /* groupCallMemberNotJoinedStatus */
}
.page_body {
background-color: #1a2026; /* groupCallBg */
}
code {
color: #ff8aac; /* historyPeer6UserpicBg */
background-color: #2c333d; /* groupCallMembersBg */
}
pre {
color: #ffffff; /* groupCallMembersFg */
background-color: #2c333d; /* groupCallMembersBg */
border: 1px solid #323a45; /* groupCallMembersBgOver */
}
.with_divider {
border-top: 1px solid #2c333d; /* groupCallMembersBg */
}
a.block_link:hover {
background-color: #323a45; /* groupCallMembersBgOver */
}
.list_page .entry {
color: #ffffff; /* groupCallMembersFg */
}
.message {
color: #ffffff; /* groupCallMembersFg */
}
div.selected {
background-color: #323a45; /* groupCallMembersBgOver */
}
.default .from_name {
color: #4db8ff; /* groupCallActiveFg */
}
.default .media .description {
color: #ffffff; /* groupCallMembersFg */
}
msgInBg,
.historyComposeAreaBg {
background-color: #2c333d; /* groupCallMembersBg */
}
msgOutBg {
background-color: #323a45; /* groupCallMembersBgOver */
}
msgInBgSelected {
background-color: #39424f; /* groupCallMembersBgRipple */
}
msgOutBgSelected {
background-color: #39424f; /* groupCallMembersBgRipple */
}
.spoiler {
background: #323a45; /* groupCallMembersBgOver */
}
.spoiler.hidden {
background: #61c0ff; /* groupCallMemberInactiveStatus */
}
.bot_button {
background-color: #4db8ff40; /* groupCallActiveFg with opacity */
}
.reactions .reaction {
background-color: #2c333d; /* groupCallMembersBg */
color: #4db8ff; /* groupCallActiveFg */
}
.reactions .reaction.active {
background-color: #4db8ff; /* groupCallActiveFg */
color: #1a2026; /* groupCallBg */
}
.reactions .reaction.paid {
background-color: #323a45; /* groupCallMembersBgOver */
color: #febb5b; /* historyPeer8UserpicBg */
}
.reactions .reaction.active.paid {
background-color: #febb5b; /* historyPeer8UserpicBg */
color: #1a2026; /* groupCallBg */
}
}

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.";
@@ -1730,6 +1735,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_block_user" = "Block user";
"lng_profile_unblock_user" = "Unblock user";
"lng_profile_export_chat" = "Export chat history";
"lng_profile_export_topic" = "Export topic history";
"lng_profile_gift_premium" = "Gift Premium";
"lng_media_selected_photo#one" = "{count} Photo";
"lng_media_selected_photo#other" = "{count} Photos";
@@ -2290,7 +2296,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.";
@@ -2952,6 +2958,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
"lng_credits_commission" = "{amount} commission";
"lng_credits_paid_messages_fee_live_reaction" = "Fee for Live Story Reaction";
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
@@ -3998,6 +4005,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 +4036,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 +4057,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";
@@ -4779,6 +4792,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_react_send" = "Send {price}";
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
"lng_paid_react_agree_link" = "Terms of Service";
"lng_paid_react_admin_cant" = "You can't send Stars to your own Live Story.";
"lng_paid_react_toast#one" = "Star Sent!";
"lng_paid_react_toast#other" = "Stars Sent!";
"lng_paid_react_toast_anonymous#one" = "Star sent anonymously!";
@@ -4800,6 +4814,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_paid_reaction_title" = "React with Stars";
"lng_paid_reaction_about" = "Highlight and pin your message by sending Stars to {name}.";
"lng_paid_reaction_button" = "Send {stars}";
"lng_paid_admin_title" = "Receive Stars from Viewers";
"lng_paid_admin_about" = "Viewers can send you Star Reactions.";
"lng_sensitive_tag" = "18+";
"lng_sensitive_title" = "18+";
@@ -6293,12 +6309,15 @@ 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";
"lng_export_option_other" = "Miscellaneous data";
"lng_export_option_other_about" = "Other types of data not mentioned above (beta).";
"lng_export_header_chats" = "Chat export settings";
"lng_export_header_topic" = "Topic export settings";
"lng_export_option_personal_chats" = "Personal chats";
"lng_export_option_bot_chats" = "Bot chats";
"lng_export_option_private_groups" = "Private groups";
@@ -6793,6 +6812,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_my_name" = "My Story";
"lng_stories_archive" = "Hide Stories";
"lng_stories_unarchive" = "Unhide Stories";
"lng_stories_view_anonymously" = "View Anonymously";
"lng_stories_row_count#one" = "{count} Story";
"lng_stories_row_count#other" = "{count} Stories";
"lng_stories_views#one" = "{count} view";
@@ -6887,6 +6907,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stealth_mode_next_about" = "Hide my views for the next 25 minutes.";
"lng_stealth_mode_unlock" = "Unlock Stealth Mode";
"lng_stealth_mode_enable" = "Enable Stealth Mode";
"lng_stealth_mode_enable_and_open" = "Enable and open the story";
"lng_stealth_mode_cooldown_in" = "Available in {left}";
"lng_stealth_mode_cooldown_tip" = "Please wait until **Stealth Mode** is ready to use again.";
"lng_stealth_mode_enabled_tip_title" = "Stealth Mode On";

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.4.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,4,0
PRODUCTVERSION 6,3,4,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.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.3.0.0"
VALUE "ProductVersion", "6.3.4.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,4,0
PRODUCTVERSION 6,3,4,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.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.3.0.0"
VALUE "ProductVersion", "6.3.4.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -146,6 +146,8 @@ Data::CreditsHistoryEntry CreditsHistoryEntryFromTL(
? starrefAmount
: CreditsAmount()),
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
.limitedCount = parsedGift ? parsedGift->limitedCount : 0,
.limitedLeft = parsedGift ? parsedGift->limitedLeft : 0,
.starsConverted = int(nonUniqueGift
? nonUniqueGift->vconvert_stars().v
: 0),

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

@@ -650,18 +650,31 @@ void EditFilterBox(
}),
anim::type::instant);
const auto &padding = st::defaultSubsectionTitlePadding;
const auto isPremium = session->premium();
const auto title = Ui::AddSubsectionTitle(
colors,
tr::lng_filters_tag_color_subtitle());
const auto preview = Ui::CreateChild<Ui::RpWidget>(colors);
title->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
const auto titleWrap = colors->add(
object_ptr<Ui::FixedHeightWidget>(
colors,
rect::m::sum::v(padding)
+ st::defaultSubsectionTitle.style.font->height));
const auto title = Ui::CreateChild<Ui::FlatLabel>(
titleWrap,
tr::lng_filters_tag_color_subtitle(),
st::defaultSubsectionTitle);
title->move(rect::m::pos::tl(padding));
const auto preview = Ui::CreateChild<Ui::RpWidget>(titleWrap);
rpl::combine(
title->sizeValue(),
titleWrap->widthValue()
) | rpl::start_with_next([=](const QSize &s, int w) {
const auto h = st::normalFont->height;
const auto left = padding.left()
+ s.width()
+ st::settingsFilterTagPreviewSkip;
preview->setGeometry(
rect::right(colors) - st::settingsFilterTagPreviewSkip,
r.y() + (r.height() - h) / 2 + st::lineWidth,
colors->width(),
left,
padding.top() + (s.height() - h) / 2,
w - left,
h);
}, preview->lifetime());
@@ -673,12 +686,16 @@ void EditFilterBox(
};
const auto tag = preview->lifetime().make_state<TagState>();
tag->context.textContext = Core::TextContext({ .session = session });
const auto shift = st::settingsFilterTagPreviewSkip / 2;
preview->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(preview);
p.setOpacity(tag->alpha);
const auto size = tag->frame.size() / style::DevicePixelRatio();
const auto rect = QRect(
preview->width() - size.width() - st::boxRowPadding.right(),
preview->width()
- size.width()
- st::boxRowPadding.right()
- shift,
(st::normalFont->height - size.height()) / 2,
size.width(),
size.height());
@@ -688,7 +705,7 @@ void EditFilterBox(
p.setFont(st::normalFont);
p.setPen(st::windowSubTextFg);
p.drawText(
preview->rect() - st::boxRowPadding,
preview->rect().translated(-shift, 0) - st::boxRowPadding,
tr::lng_filters_tag_color_no(tr::now),
style::al_right);
}

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();
@@ -1598,11 +1616,30 @@ void AddCreditsHistoryEntryTable(
: entry.giftUpgraded
? tr::lng_credits_box_history_entry_gift_from()
: tr::lng_credits_box_history_entry_peer();
const auto targetId = actorId ? actorId : peerId;
const auto isPeerDefault = !entry.starrefCommission
&& !entry.in
&& !entry.giftResale
&& !entry.giftUpgraded;
const auto user = isPeerDefault
? session->data().peer(targetId)->asUser()
: nullptr;
const auto withSendButton = user
&& !user->isInaccessible()
&& !user->isBot();
auto send = withSendButton ? tr::lng_gift_send_small() : nullptr;
auto handler = send
? Fn<void()>([=] {
if (const auto window = show->resolveWindow()) {
Ui::ShowStarGiftBox(window, user);
}
})
: nullptr;
AddTableRow(
table,
std::move(text),
show,
actorId ? actorId : peerId);
MakePeerTableValue(table, show, targetId, send, handler),
st::giveawayGiftCodePeerMargin);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto peer = session->data().peer(peerId);
@@ -1762,6 +1799,19 @@ void AddCreditsHistoryEntryTable(
rpl::single(
Ui::Text::Link(entry.successLink, entry.successLink)));
}
if (entry.limitedCount > 0 && entry.limitedLeft >= 0) {
AddTableRow(
table,
tr::lng_gift_availability(),
tr::lng_gift_availability_left(
lt_count_decimal,
rpl::single(entry.limitedLeft) | tr::to_count(),
lt_amount,
rpl::single(TextWithEntities{
Lang::FormatCountDecimal(entry.limitedCount)
}),
Ui::Text::WithEntities));
}
}
void AddSubscriptionEntryTable(

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

@@ -2294,6 +2294,9 @@ void Controller::saveUsernamesOrder() {
continueSave();
}).send();
} else {
const auto weakContinue = crl::guard(this, [=] {
continueSave();
});
const auto lifetime = std::make_shared<rpl::lifetime>();
const auto newUsernames = (*_savingData.usernamesOrder);
_peer->session().api().usernames().reorder(
@@ -2311,7 +2314,7 @@ void Controller::saveUsernamesOrder() {
.editable = editable,
};
}) | ranges::to_vector);
continueSave();
weakContinue();
lifetime->destroy();
}, *lifetime);
}

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([=] {
@@ -1731,6 +1732,49 @@ void CheckMaybeGiftLocked(
.forceTon = star->forceTon,
},
Settings::CreditsEntryBoxStyleOverrides()));
} else if (unique && star->mine && !peer->isSelf()) {
if (ShowTransferGiftLater(window->uiShow(), unique)) {
return;
}
const auto done = [=] {
window->session().credits().load(true);
window->showPeerHistory(peer);
};
if (state->transferRequested == unique) {
return;
}
state->transferRequested = unique;
const auto savedId = star->transferId;
using Payments::CheckoutResult;
const auto formReady = [=](
uint64 formId,
CreditsAmount price,
std::optional<CheckoutResult> failure) {
state->transferRequested = nullptr;
if (!failure && !price.stars()) {
LOG(("API Error: "
"Bad transfer invoice currenct."));
} else if (!failure
|| *failure == CheckoutResult::Free) {
unique->starsForTransfer = failure
? 0
: price.whole();
ShowTransferToBox(
window,
peer,
unique,
savedId,
done);
} else if (*failure == CheckoutResult::Cancelled) {
done();
}
};
RequestOurForm(
window->uiShow(),
MTP_inputInvoiceStarGiftTransfer(
Api::InputSavedStarGiftId(savedId, unique),
peer->input),
formReady);
} else if (star && star->resale) {
const auto id = star->info.id;
if (state->resaleRequestingId == id) {
@@ -1780,49 +1824,6 @@ void CheckMaybeGiftLocked(
}
});
CheckMaybeGiftLocked(window, star->info.id, ready);
} else if (unique && star->mine && !peer->isSelf()) {
if (ShowTransferGiftLater(window->uiShow(), unique)) {
return;
}
const auto done = [=] {
window->session().credits().load(true);
window->showPeerHistory(peer);
};
if (state->transferRequested == unique) {
return;
}
state->transferRequested = unique;
const auto savedId = star->transferId;
using Payments::CheckoutResult;
const auto formReady = [=](
uint64 formId,
CreditsAmount price,
std::optional<CheckoutResult> failure) {
state->transferRequested = nullptr;
if (!failure && !price.stars()) {
LOG(("API Error: "
"Bad transfer invoice currenct."));
} else if (!failure
|| *failure == CheckoutResult::Free) {
unique->starsForTransfer = failure
? 0
: price.whole();
ShowTransferToBox(
window,
peer,
unique,
savedId,
done);
} else if (*failure == CheckoutResult::Cancelled) {
done();
}
};
RequestOurForm(
window->uiShow(),
MTP_inputInvoiceStarGiftTransfer(
Api::InputSavedStarGiftId(savedId, unique),
peer->input),
formReady);
} else if (star
&& star->info.perUserTotal
&& !star->info.perUserRemains) {
@@ -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

@@ -1698,6 +1698,7 @@ groupCallMessagesScroll: ScrollArea(defaultScrollArea) {
barHidden: true;
}
groupCallMessagePadding: margins(8px, 3px, 8px, 2px);
groupCallPricePadding: margins(5px, 0px, 5px, 0px);
groupCallMessageSkip: 8px;
groupCallMessagePalette: TextPalette(defaultTextPalette) {
linkFg: radialFg;
@@ -1721,17 +1722,15 @@ groupCallUserpicPadding: margins(2px, 2px, 4px, 2px);
groupCallPinnedPadding: margins(10px, 4px, 10px, 2px);
groupCallPinnedMaxWidth: 96px;
groupCallPinnedUserpic: 22px;
groupCallEffectPadding: margins(3px, 1px, 3px, 1px);
groupCallEffectUserpicPadding: margins(1px, 1px, 3px, 1px);
groupCallTopReactorBadge: RoundCheckbox(defaultRoundCheckbox) {
width: 1px;
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

@@ -617,9 +617,16 @@ void SetupFingerprintTooltip(not_null<Ui::RpWidget*> widget) {
widget->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto type = e->type();
if (type == QEvent::Enter) {
state->toggleTooltip(true);
// Enter events may come from widget destructors,
// in that case sync-showing tooltip (calling Grab)
// crashes the whole thing.
crl::on_main(widget, [=] {
state->toggleTooltip(true);
});
} else if (type == QEvent::Leave) {
state->toggleTooltip(false);
crl::on_main(widget, [=] {
state->toggleTooltip(false);
});
}
}, widget->lifetime());
}

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

@@ -69,12 +69,6 @@ constexpr auto kStarsStatsShortPollDelay = 30 * crl::time(1000);
return PinFinishDate(message.peer, message.date, message.stars);
}
[[nodiscard]] std::optional<PeerId> MaybeShownPeer(
uint32 privacySet,
PeerId shownPeer) {
return privacySet ? shownPeer : std::optional<PeerId>();
}
} // namespace
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
@@ -104,9 +98,7 @@ Messages::~Messages() {
finishPaidSending({
.count = int(_paid.sending),
.valid = true,
.shownPeer = MaybeShownPeer(
_paid.sendingPrivacySet,
_paid.sendingShownPeer),
.shownPeer = _paid.sendingShownPeer,
}, false);
}
}
@@ -164,6 +156,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 +166,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 +226,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 +262,7 @@ void Messages::received(const MTPDupdateGroupCallEncryptedMessage &data) {
deserialized->message,
base::unixtime::now(), // date
0, // stars
false,
true); // checkCustomEmoji
}
@@ -332,6 +327,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 +377,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);
@@ -513,26 +509,23 @@ PeerId Messages::reactionsLocalShownPeer() const {
return entry.peer ? entry.peer->id : PeerId();
}
}
return _session->userPeerId();
return _call->messagesFrom()->id;
//const auto api = &_session->api();
//return api->globalPrivacy().paidReactionShownPeerCurrent();
};
return (_paid.scheduledFlag && _paid.scheduledPrivacySet)
return _paid.scheduledFlag
? _paid.scheduledShownPeer
: (_paid.sendingFlag && _paid.sendingPrivacySet)
: _paid.sendingFlag
? _paid.sendingShownPeer
: minePaidShownPeer();
}
void Messages::reactionsPaidAdd(int count, std::optional<PeerId> shownPeer) {
void Messages::reactionsPaidAdd(int count) {
Expects(count >= 0);
_paid.scheduled += count;
_paid.scheduledFlag = 1;
if (shownPeer.has_value()) {
_paid.scheduledShownPeer = *shownPeer;
_paid.scheduledPrivacySet = true;
}
_paid.scheduledShownPeer = _call->messagesFrom()->id;
if (count > 0) {
_session->credits().lock(CreditsAmount(count));
}
@@ -551,7 +544,6 @@ void Messages::reactionsPaidScheduledCancel() {
_paid.scheduled = 0;
_paid.scheduledFlag = 0;
_paid.scheduledShownPeer = 0;
_paid.scheduledPrivacySet = 0;
_paidChanges.fire({});
}
@@ -566,17 +558,13 @@ Data::PaidReactionSend Messages::startPaidReactionSending() {
_paid.sending = _paid.scheduled;
_paid.sendingFlag = _paid.scheduledFlag;
_paid.sendingShownPeer = _paid.scheduledShownPeer;
_paid.sendingPrivacySet = _paid.scheduledPrivacySet;
_paid.scheduled = 0;
_paid.scheduledFlag = 0;
_paid.scheduledShownPeer = 0;
_paid.scheduledPrivacySet = 0;
return {
.count = int(_paid.sending),
.valid = true,
.shownPeer = MaybeShownPeer(
_paid.sendingPrivacySet,
_paid.sendingShownPeer),
.shownPeer = _paid.sendingShownPeer,
};
}
@@ -585,25 +573,24 @@ void Messages::finishPaidSending(
bool success) {
Expects(send.count == _paid.sending);
Expects(send.valid == (_paid.sendingFlag == 1));
Expects(send.shownPeer == MaybeShownPeer(
_paid.sendingPrivacySet,
_paid.sendingShownPeer));
Expects(send.shownPeer == _paid.sendingShownPeer);
_paid.sending = 0;
_paid.sendingFlag = 0;
_paid.sendingShownPeer = 0;
_paid.sendingPrivacySet = 0;
if (const auto amount = send.count) {
if (success) {
const auto from = _session->data().peer(*send.shownPeer);
_session->credits().withdrawLocked(CreditsAmount(amount));
auto &donors = _paid.top.topDonors;
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
const auto i = ranges::find(donors, true, &StarsDonor::my);
if (i != end(donors)) {
i->peer = from;
i->stars += amount;
} else {
donors.push_back({
.peer = _session->user(),
.peer = from,
.stars = amount,
.my = true,
});
@@ -628,7 +615,7 @@ void Messages::reactionsPaidSend() {
const auto randomId = base::RandomValue<uint64>();
_sendingIdByRandomId.emplace(randomId, localId);
const auto from = _call->messagesFrom();
const auto from = _session->data().peer(*send.shownPeer);
const auto stars = int(send.count);
const auto skip = skipMessage({}, stars);
if (skip) {
@@ -672,7 +659,7 @@ void Messages::undoScheduledPaidOnDestroy() {
Messages::PaidLocalState Messages::starsLocalState() const {
const auto &donors = _paid.top.topDonors;
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
const auto i = ranges::find(donors, true, &StarsDonor::my);
const auto local = int(_paid.scheduled);
const auto my = (i != end(donors) ? i->stars : 0) + local;
const auto total = _paid.top.total + local;
@@ -724,7 +711,7 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
const auto i = ranges::find(
_paid.top.topDonors,
from.get(),
&StarsTopDonor::peer);
&StarsDonor::peer);
if (i != end(_paid.top.topDonors)) {
i->stars += stars;
} else {
@@ -737,8 +724,8 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
ranges::stable_sort(
_paid.top.topDonors,
ranges::greater(),
&StarsTopDonor::stars);
_paidChanges.fire({});
&StarsDonor::stars);
_paidChanges.fire({ .peer = from, .stars = stars });
}
} // namespace Calls::Group

View File

@@ -54,18 +54,18 @@ struct MessageDeleteRequest {
bool reportSpam = false;
};
struct StarsTopDonor {
struct StarsDonor {
PeerData *peer = nullptr;
int stars = 0;
bool my = false;
friend inline bool operator==(
const StarsTopDonor &,
const StarsTopDonor &) = default;
const StarsDonor &,
const StarsDonor &) = default;
};
struct StarsTop {
std::vector<StarsTopDonor> topDonors;
std::vector<StarsDonor> topDonors;
int total = 0;
friend inline bool operator==(
@@ -91,7 +91,7 @@ public:
[[nodiscard]] int reactionsPaidScheduled() const;
[[nodiscard]] PeerId reactionsLocalShownPeer() const;
void reactionsPaidAdd(int count, std::optional<PeerId> shownPeer = {});
void reactionsPaidAdd(int count);
void reactionsPaidScheduledCancel();
void reactionsPaidSend();
void undoScheduledPaidOnDestroy();
@@ -101,7 +101,7 @@ public:
int my = 0;
};
[[nodiscard]] PaidLocalState starsLocalState() const;
[[nodiscard]] rpl::producer<> starsValueChanges() const {
[[nodiscard]] rpl::producer<StarsDonor> starsValueChanges() const {
return _paidChanges.events();
}
[[nodiscard]] const StarsTop &starsTop() const {
@@ -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);
@@ -154,7 +155,9 @@ private:
const TextWithEntities &text,
int stars) const;
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
void finishPaidSending(Data::PaidReactionSend send, bool success);
void finishPaidSending(
Data::PaidReactionSend send,
bool success);
void addStars(not_null<PeerData*> from, int stars, bool mine);
void requestStarsStats();
@@ -180,7 +183,7 @@ private:
mtpRequestId _starsTopRequestId = 0;
Paid _paid;
rpl::event_stream<> _paidChanges;
rpl::event_stream<StarsDonor> _paidChanges;
bool _paidSendingPending = false;
TimeId _ttl = 0;

View File

@@ -73,6 +73,13 @@ constexpr auto kAdminBadgeTextOpacity = 0.6;
return minHeight / 2;
}
[[nodiscard]] int CountPriceRadius() {
const auto height = st::groupCallPricePadding.top()
+ st::normalFont->height
+ st::groupCallPricePadding.bottom();
return height / 2;
}
[[nodiscard]] int CountPinnedRadius() {
const auto height = st::groupCallUserpicPadding.top()
+ st::groupCallPinnedUserpic
@@ -304,6 +311,7 @@ struct MessagesUi::MessageView {
bool removed = false;
bool sending = false;
bool failed = false;
bool simple = false;
bool admin = false;
bool mine = false;
};
@@ -333,6 +341,7 @@ MessagesUi::PayedBg::PayedBg(const Ui::StarsColoring &coloring)
, pinnedLight(CountPinnedRadius(), light.color())
, pinnedDark(CountPinnedRadius(), dark.color())
, messageLight(CountMessageRadius(), light.color())
, priceDark(CountPriceRadius(), dark.color())
, badgeDark(st::roundRadiusLarge, dark.color()) {
}
@@ -518,6 +527,7 @@ void MessagesUi::animateMessageSent(MessageView &entry) {
void MessagesUi::updateMessageSize(MessageView &entry) {
const auto &padding = st::groupCallMessagePadding;
const auto &pricePadding = st::groupCallPricePadding;
const auto hasUserpic = !entry.failed;
const auto userpicPadding = st::groupCallUserpicPadding;
@@ -532,6 +542,9 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
entry.text,
std::min(st::groupCallWidth / 2, inner),
inner);
const auto price = entry.simple
? (pricePadding.left() + pricePadding.right() + entry.price.maxWidth())
: 0;
const auto space = st::normalFont->spacew;
const auto nameWidth = entry.name.isEmpty() ? 0 : entry.name.maxWidth();
const auto nameLineWidth = nameWidth
@@ -546,12 +559,19 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
? 0
: st::messageTextStyle.font->height;
const auto textHeight = size.height();
entry.width = std::max(size.width(), std::min(nameLineWidth, inner))
+ widthSkip;
entry.width = widthSkip
+ std::max(size.width() + price, std::min(nameLineWidth, inner));
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();
@@ -630,6 +650,8 @@ void MessagesUi::setContentFailed(MessageView &entry) {
}
void MessagesUi::setContent(MessageView &entry) {
entry.simple = !entry.admin && entry.original.empty() && entry.stars > 0;
const auto name = nameText(entry.from, entry.place);
entry.name = entry.admin
? Ui::Text::String(
@@ -641,7 +663,7 @@ void MessagesUi::setContent(MessageView &entry) {
: Ui::Text::String();
if (const auto stars = entry.stars) {
entry.price = Ui::Text::String(
st::whoReadDateStyle,
entry.simple ? st::messageTextStyle : st::whoReadDateStyle,
Ui::Text::IconEmoji(
&st::starIconEmojiSmall
).append(Lang::FormatCountDecimal(stars)),
@@ -661,12 +683,14 @@ void MessagesUi::setContent(MessageView &entry) {
kMarkupTextOptions,
st::groupCallWidth / 8,
_crownHelper.context([this, id = entry.id] { repaintMessage(id); }));
if (!entry.price.isEmpty()) {
if (!entry.simple && !entry.price.isEmpty()) {
entry.text.updateSkipBlock(
entry.price.maxWidth(),
st::normalFont->height);
}
entry.text.setLink(1, entry.fromLink);
if (!entry.simple && !entry.admin) {
entry.text.setLink(1, entry.fromLink);
}
if (entry.text.hasSpoilers()) {
const auto id = entry.id;
const auto guard = base::make_weak(_messages);
@@ -1200,18 +1224,19 @@ void MessagesUi::setupMessagesWidget() {
p.setOpacity(scale);
p.translate(-mx, -my);
}
auto bg = (std::unique_ptr<PayedBg>*)nullptr;
if (!_streamMode) {
_messageBgRect.paint(p, { x, y, width, use });
} else if (entry.stars) {
const auto coloring = Ui::StarsColoringForCount(
colorings,
entry.stars);
auto &bg = _bgs[ColoringKey(coloring)];
if (!bg) {
bg = std::make_unique<PayedBg>(coloring);
bg = &_bgs[ColoringKey(coloring)];
if (!*bg) {
*bg = std::make_unique<PayedBg>(coloring);
}
p.setOpacity(kColoredMessageBgOpacity);
bg->messageLight.paint(p, { x, y, width, use });
(*bg)->messageLight.paint(p, { x, y, width, use });
p.setOpacity(1.);
if (_highlightAnimation.animating()
&& entry.id == _highlightId) {
@@ -1229,7 +1254,6 @@ void MessagesUi::setupMessagesWidget() {
}
const auto textLeft = entry.textLeft;
const auto priceSkip = padding.right() / 2;
const auto hasUserpic = !entry.failed;
if (hasUserpic) {
const auto userpicSize = st::groupCallUserpic;
@@ -1275,6 +1299,7 @@ void MessagesUi::setupMessagesWidget() {
},
.availableWidth = entry.nameWidth,
.palette = &st::groupCallMessagePalette,
.elisionLines = 1,
});
const auto liveLeft = x + textLeft + entry.nameWidth + space;
_liveBadge.draw(p, {
@@ -1291,23 +1316,49 @@ void MessagesUi::setupMessagesWidget() {
});
p.setOpacity(1.);
}
const auto pricePadding = st::groupCallPricePadding;
const auto textRight = padding.right()
+ (entry.simple
? (entry.price.maxWidth()
+ pricePadding.left()
+ pricePadding.right())
: 0);
entry.text.draw(p, {
.position = {
x + textLeft,
y + entry.textTop,
},
.availableWidth = entry.width - textLeft - padding.right(),
.availableWidth = entry.width - textLeft - textRight,
.palette = &st::groupCallMessagePalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = now,
.paused = !_messages->window()->isActiveWindow(),
});
if (!entry.price.isEmpty()) {
const auto priceRight = x
+ entry.width
- entry.price.maxWidth();
const auto priceLeft = entry.simple
? (priceRight
- (padding.top() - pricePadding.top())
- pricePadding.right())
: (priceRight - (padding.right() / 2));
const auto priceTop = entry.simple
? (y + entry.textTop)
: (y + use - st::normalFont->height);
if (entry.simple && bg) {
p.setOpacity(kDarkOverOpacity);
const auto r = QRect(
priceLeft,
priceTop,
entry.price.maxWidth(),
st::normalFont->height
).marginsAdded(pricePadding);
(*bg)->priceDark.paint(p, r);
p.setOpacity(1.);
}
entry.price.draw(p, {
.position = {
x + entry.width - entry.price.maxWidth() - priceSkip,
y + use - st::normalFont->height,
},
.position = { priceLeft, priceTop },
.availableWidth = entry.price.maxWidth(),
});
}

View File

@@ -80,6 +80,7 @@ private:
Ui::RoundRect pinnedLight;
Ui::RoundRect pinnedDark;
Ui::RoundRect messageLight;
Ui::RoundRect priceDark;
Ui::RoundRect badgeDark;
};

View File

@@ -2111,10 +2111,18 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
}
widget->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::Enter) {
trackControlOver(widget, true);
} else if (e->type() == QEvent::Leave) {
trackControlOver(widget, false);
const auto type = e->type();
if (type == QEvent::Enter) {
// Enter events may come from widget destructors,
// in that case sync-showing tooltip (calling Grab)
// crashes the whole thing.
crl::on_main(widget, [=] {
trackControlOver(widget, true);
});
} else if (type == QEvent::Leave) {
crl::on_main(widget, [=] {
trackControlOver(widget, false);
});
}
}, lifetime);
}

View File

@@ -43,18 +43,21 @@ void VideoStreamStarsBox(
VideoStreamStarsBoxArgs &&args) {
args.show->session().credits().load();
const auto admin = args.admin;
const auto sending = args.sending;
auto submitText = [=](rpl::producer<int> amount) {
auto nice = std::move(amount) | rpl::map([=](int count) {
return Ui::CreditsEmojiSmall().append(
Lang::FormatCountDecimal(count));
});
return (sending
? tr::lng_paid_reaction_button
: tr::lng_paid_comment_button)(
lt_stars,
std::move(nice),
Ui::Text::RichLangValue);
return admin
? tr::lng_box_ok(tr::marked)
: (sending
? tr::lng_paid_reaction_button
: tr::lng_paid_comment_button)(
lt_stars,
std::move(nice),
tr::rich);
};
const auto &show = args.show;
const auto session = &show->session();
@@ -121,14 +124,17 @@ void VideoStreamStarsBox(
.submit = std::move(submitText),
.colorings = show->session().appConfig().groupCallColorings(),
.balanceValue = session->credits().balanceValue(),
.send = [weak, save = args.save](int count, uint64 barePeerId) {
save(count);
.send = [=, save = args.save](int count, uint64 barePeerId) {
if (!admin) {
save(count);
}
if (const auto strong = weak.get()) {
strong->closeBox();
}
},
.videoStreamChoosing = !sending,
.videoStreamSending = sending,
.videoStreamAdmin = admin,
.dark = true,
});
}

View File

@@ -39,6 +39,7 @@ struct VideoStreamStarsBoxArgs {
int min = 0;
int current = 0;
bool sending = false;
bool admin = false;
Fn<void(int)> save;
QString name;
};

View File

@@ -65,14 +65,18 @@ Viewport::Viewport(
}
Viewport::~Viewport() {
if (_borrowed && _opengl) {
const auto w = static_cast<QOpenGLWidget*>(widget().get());
w->makeCurrent();
const auto context = w->context();
const auto valid = w->isValid()
&& context
&& (QOpenGLContext::currentContext() == context);
ensureBorrowedCleared(valid ? context->functions() : nullptr);
if (_borrowed) {
if (_opengl) {
const auto w = static_cast<QOpenGLWidget*>(widget().get());
w->makeCurrent();
const auto context = w->context();
const auto valid = w->isValid()
&& context
&& (QOpenGLContext::currentContext() == context);
ensureBorrowedCleared(valid ? context->functions() : nullptr);
} else {
ensureBorrowedCleared();
}
}
}
@@ -787,13 +791,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
top += height + st::groupCallVideoSmallSkip;
}
};
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
const auto topPeer = _large ? _large->peer().get() : nullptr;
const auto reorderNeeded = [&] {
if (!topPeer) {
return false;
}
for (const auto &tile : _tiles) {
if (tile.get() != _large && tile->row()->peer() == topPeer) {
if (tile.get() != _large && tile->peer() == topPeer) {
return (tile.get() != _tiles.front().get())
&& !tile->trackOrUserpicSize().isEmpty();
}
@@ -809,7 +813,7 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
ranges::stable_partition(
_tilesForOrder,
[&](not_null<VideoTile*> tile) {
return (tile->row()->peer() == topPeer);
return (tile->peer() == topPeer);
});
for (const auto &tile : _tilesForOrder) {
layoutNext(tile);
@@ -925,6 +929,7 @@ rpl::producer<bool> Viewport::mouseInsideValue() const {
void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
Expects(_borrowed != nullptr);
Expects(_opengl);
if (_borrowedRenderer) {
return;
@@ -935,6 +940,7 @@ void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
void Viewport::ensureBorrowedCleared(QOpenGLFunctions *f) {
Expects(_borrowed != nullptr);
Expects(_opengl);
if (const auto renderer = base::take(_borrowedRenderer)) {
renderer->deinit(f);
@@ -948,6 +954,23 @@ void Viewport::borrowedPaint(QOpenGLFunctions &f) {
_borrowedRenderer->paint(static_cast<QOpenGLWidget*>(widget().get()), f);
}
void Viewport::ensureBorrowedRenderer() {
Expects(_borrowed != nullptr);
Expects(!_opengl);
if (_borrowedRenderer) {
return;
}
_borrowedRenderer = makeRenderer();
}
void Viewport::ensureBorrowedCleared() {
Expects(_borrowed != nullptr);
Expects(!_opengl);
base::take(_borrowedRenderer);
}
void Viewport::borrowedPaint(Painter &p, const QRegion &clip) {
Expects(_borrowedRenderer != nullptr);
Expects(!_opengl);

View File

@@ -103,7 +103,11 @@ public:
void ensureBorrowedRenderer(QOpenGLFunctions &f);
void ensureBorrowedCleared(QOpenGLFunctions *f);
void borrowedPaint(QOpenGLFunctions &f);
void ensureBorrowedRenderer();
void ensureBorrowedCleared();
void borrowedPaint(Painter &p, const QRegion &clip);
[[nodiscard]] QPoint borrowedOrigin() const;
[[nodiscard]] rpl::lifetime &lifetime();

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) {
@@ -458,7 +460,7 @@ void Viewport::RendererGL::validateUserpicFrame(
}
const auto size = tile->trackOrUserpicSize();
tileData.userpicFrame = PeerData::GenerateUserpicImage(
tile->row()->peer(),
tile->peer(),
tile->row()->ensureUserpicView(),
size.width(),
0);
@@ -1237,7 +1239,7 @@ void Viewport::RendererGL::validateDatas() {
j->stale = false;
const auto index = (j - begin(_tileData));
_tileDataIndices[i] = index;
const auto peer = tiles[i]->row()->peer();
const auto peer = tiles[i]->peer();
if ((j->peer != peer)
|| (j->nameVersion != peer->nameVersion())
|| (j->nameRect.width() != width)) {
@@ -1261,7 +1263,7 @@ void Viewport::RendererGL::validateDatas() {
continue;
}
const auto id = quintptr(tiles[i]->track().get());
const auto peer = tiles[i]->row()->peer();
const auto peer = tiles[i]->peer();
const auto paused = (tiles[i]->track()->state()
== Webrtc::VideoState::Paused);
auto index = int(_tileData.size());

View File

@@ -52,12 +52,14 @@ void Viewport::RendererSW::paintFallback(
}
paintTile(p, tile.get(), bounding, bg);
}
const auto fullscreen = _owner->_fullscreen;
const auto color = fullscreen
? QColor(0, 0, 0)
: st::groupCallBg->c;
for (const auto &rect : bg) {
p.fillRect(rect, color);
if (_owner->borrowedOrigin().isNull()) {
const auto fullscreen = _owner->_fullscreen;
const auto color = fullscreen
? QColor(0, 0, 0)
: st::groupCallBg->c;
for (const auto &rect : bg) {
p.fillRect(rect, color);
}
}
for (auto i = _tileData.begin(); i != _tileData.end();) {
if (i->second.stale) {
@@ -80,7 +82,7 @@ void Viewport::RendererSW::validateUserpicFrame(
const auto size = tile->trackOrUserpicSize();
data.userpicFrame = Images::BlurLargeImage(
PeerData::GenerateUserpicImage(
tile->row()->peer(),
tile->peer(),
tile->row()->ensureUserpicView(),
size.width(),
0),

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_viewport_tile.h"
#include "calls/group/calls_group_members_row.h"
#include "webrtc/webrtc_video_track.h"
#include "lang/lang_keys.h"
#include "ui/round_rect.h"
@@ -33,11 +34,11 @@ Viewport::VideoTile::VideoTile(
: _endpoint(endpoint)
, _update(std::move(update))
, _track(std::move(track))
, _peer(_track.row->peer())
, _trackSize(std::move(trackSize))
, _rtmp(endpoint.rtmp())
, _self(self) {
Expects(_track.track != nullptr);
Expects(_track.row != nullptr);
using namespace rpl::mappers;
_track.track->stateValue(

View File

@@ -37,6 +37,9 @@ public:
[[nodiscard]] not_null<MembersRow*> row() const {
return _track.row;
}
[[nodiscard]] not_null<PeerData*> peer() const {
return _peer;
}
[[nodiscard]] bool rtmp() const {
return _rtmp;
}
@@ -113,8 +116,9 @@ private:
const VideoEndpoint _endpoint;
const Fn<void()> _update;
const VideoTileTrack _track;
const not_null<PeerData*> _peer;
VideoTileTrack _track;
QRect _geometry;
TileAnimation _animation;
rpl::variable<QSize> _trackSize;

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

@@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
const auto kSets = {
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
Set{ { 1, 2774, 8'455'034, "Android" }, PreviewPath(1) },
Set{ { 2, 2775, 5'713'503, "Twemoji" }, PreviewPath(2) },
Set{ { 3, 2776, 7'347'332, "JoyPixels" }, PreviewPath(3) },
};
using Loading = MTP::DedicatedLoader::Progress;

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 = 6003004;
constexpr auto AppVersionStr = "6.3.4";
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

@@ -40,6 +40,10 @@ struct CreditsHistoryEntry final {
return !id.isEmpty();
}
[[nodiscard]] bool isLiveStoryReaction() const {
return paidMessagesCount && reaction && !bareMsgId;
}
using PhotoId = uint64;
enum class PeerType {
Peer,
@@ -104,11 +108,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

@@ -449,7 +449,7 @@ QImage *PeerData::userpicCloudImage(Ui::PeerUserpicView &view) const {
}
void PeerData::paintUserpic(
Painter &p,
QPainter &p,
Ui::PeerUserpicView &view,
PaintUserpicContext context) const {
if (const auto broadcast = monoforumBroadcast()) {

View File

@@ -387,11 +387,11 @@ public:
void setUserpicPhoto(const MTPPhoto &data);
void paintUserpic(
Painter &p,
QPainter &p,
Ui::PeerUserpicView &view,
PaintUserpicContext context) const;
void paintUserpic(
Painter &p,
QPainter &p,
Ui::PeerUserpicView &view,
int x,
int y,
@@ -406,7 +406,7 @@ public:
});
}
void paintUserpicLeft(
Painter &p,
QPainter &p,
Ui::PeerUserpicView &view,
int x,
int y,

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

@@ -1062,14 +1062,15 @@ void Widget::setupTopBarSuggestions(not_null<Ui::VerticalLayout*> dialogs) {
return;
}
using namespace rpl::mappers;
crl::on_main(&session(), [=, session = &session()] {
session->api().authorizations().unreviewedChanges(
crl::on_main(dialogs, [=] {
const auto owner = &session().data();
session().api().authorizations().unreviewedChanges(
) | rpl::start_with_next([=] {
updateForceDisplayWide();
}, lifetime());
(session->data().chatsListLoaded(nullptr)
(owner->chatsListLoaded(nullptr)
? rpl::single<Data::Folder*>(nullptr)
: session->data().chatsListLoadedEvents()
: owner->chatsListLoadedEvents()
) | rpl::filter(_1 == nullptr) | rpl::map([=] {
auto on = rpl::combine(
controller()->activeChatsFilter(),
@@ -1090,9 +1091,9 @@ void Widget::setupTopBarSuggestions(not_null<Ui::VerticalLayout*> dialogs) {
&& wide
&& !search
&& !searchInPeer
&& (id == session->data().chatsFilters().defaultId());
&& (id == owner->chatsFilters().defaultId());
});
return TopBarSuggestionValue(dialogs, session, std::move(on));
return TopBarSuggestionValue(dialogs, &session(), std::move(on));
}) | rpl::flatten_latest() | rpl::start_with_next([=](
Ui::SlideWrap<Ui::RpWidget> *raw) {
if (raw) {

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "dialogs/ui/dialogs_stories_content.h"
#include "base/unixtime.h"
#include "data/data_changes.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
@@ -21,12 +22,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "main/main_session.h"
#include "media/stories/media_stories_stealth.h"
#include "lang/lang_keys.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_media_stories.h"
namespace Dialogs::Stories {
namespace {
@@ -243,6 +247,23 @@ void FillSourceMenu(
add(viewProfileText, [=] {
controller->showPeerInfo(peer);
}, channel ? &st::menuIconInfo : &st::menuIconProfile);
if (peer->session().premiumPossible()
&& peer->isUser()
&& !peer->hasActiveVideoStream()
&& peer->hasUnreadStories()) {
const auto now = base::unixtime::now();
const auto stealth = owner->stories().stealthMode();
add(tr::lng_stories_view_anonymously(tr::now), [=] {
Media::Stories::SetupStealthMode(
controller->uiShow(),
Media::Stories::StealthModeDescriptor{
[=] { controller->openPeerStories(peer->id); },
&st::storiesStealthStyleDefault,
});
}, ((peer->session().premium() || (stealth.enabledTill > now))
? &st::menuIconStealth
: &st::menuIconStealthLocked));
}
const auto in = [&](Data::StorySourcesList list) {
return ranges::contains(
owner->stories().sources(list),

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;
@@ -210,25 +225,41 @@ struct ApiWrap::DialogsProcess : ChatsProcess {
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
};
struct ApiWrap::ChatProcess {
Data::DialogInfo info;
FnMut<bool(const Data::DialogInfo &)> start;
struct ApiWrap::AbstractMessagesProcess {
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::MessagesSlice&&)> handleSlice;
FnMut<void()> done;
FnMut<void(MTPmessages_Messages&&)> requestDone;
int localSplitIndex = 0;
int32 largestIdPlusOne = 1;
Data::ParseMediaContext context;
std::optional<Data::MessagesSlice> slice;
bool lastSlice = false;
int fileIndex = 0;
};
struct ApiWrap::ChatProcess : AbstractMessagesProcess {
Data::DialogInfo info;
FnMut<bool(const Data::DialogInfo &)> start;
int localSplitIndex = 0;
int32 largestIdPlusOne = 1;
};
struct ApiWrap::TopicProcess : AbstractMessagesProcess {
PeerId peerId = 0;
MTPInputPeer inputPeer;
int32 topicRootId = 0;
QString relativePath;
FnMut<bool(int count)> start;
int32 offsetId = 0;
int totalCount = 0;
int processedCount = 0;
};
template <typename Request>
class ApiWrap::RequestBuilder {
@@ -438,6 +469,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 +502,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 +554,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 +1126,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);
@@ -1823,13 +2096,18 @@ std::optional<QByteArray> ApiWrap::getCustomEmoji(QByteArray &data) {
}
auto &file = i->second.file;
const auto fileProgress = [=](FileProgress value) {
return loadMessageEmojiProgress(value);
if (_chatProcess) {
return loadMessageEmojiProgress(value);
} else if (_topicProcess) {
return loadTopicEmojiProgress(value);
}
return true;
};
const auto ready = processFileLoad(
file,
{ .customEmojiId = id },
fileProgress,
[=](const QString &path) { loadMessageEmojiDone(id, path); });
[=](const QString &path) { loadCustomEmojiDone(id, path); });
if (!ready) {
return std::nullopt;
}
@@ -2005,6 +2283,36 @@ void ApiWrap::loadMessageEmojiDone(uint64 id, const QString &relativePath) {
loadNextMessageFile();
}
bool ApiWrap::loadTopicEmojiProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
Expects((_topicProcess->fileIndex >= 0)
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
return _topicProcess->fileProgress(DownloadProgress{
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _topicProcess->fileIndex,
.ready = progress.ready,
.total = progress.total });
}
void ApiWrap::loadCustomEmojiDone(uint64 id, const QString &relativePath) {
const auto i = _resolvedCustomEmoji.find(id);
if (i != end(_resolvedCustomEmoji)) {
i->second.file.relativePath = relativePath;
if (relativePath.isEmpty()) {
i->second.file.skipReason = Data::File::SkipReason::Unavailable;
}
}
if (_chatProcess) {
loadNextMessageFile();
} else if (_topicProcess) {
loadNextTopicMessageFile();
}
}
void ApiWrap::finishMessages() {
Expects(_chatProcess != nullptr);
Expects(!_chatProcess->slice.has_value());
@@ -2013,6 +2321,316 @@ void ApiWrap::finishMessages() {
process->done();
}
void ApiWrap::requestTopicMessages(
PeerId peerId,
MTPInputPeer inputPeer,
int32 topicRootId,
FnMut<bool(int count)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done) {
Expects(_topicProcess == nullptr);
Expects(_selfId.has_value());
_topicProcess = std::make_unique<TopicProcess>();
_topicProcess->context.selfPeerId = peerFromUser(*_selfId);
_topicProcess->peerId = peerId;
_topicProcess->inputPeer = inputPeer;
_topicProcess->topicRootId = topicRootId;
_topicProcess->relativePath = "chats/chat_"
+ QString::number(peerId.value)
+ "/topic_"
+ QString::number(topicRootId)
+ "/";
_topicProcess->start = std::move(start);
_topicProcess->fileProgress = std::move(progress);
_topicProcess->handleSlice = std::move(slice);
_topicProcess->done = std::move(done);
mainRequest(MTPchannels_GetMessages(
MTP_inputChannel(
inputPeer.c_inputPeerChannel().vchannel_id(),
inputPeer.c_inputPeerChannel().vaccess_hash()),
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(topicRootId)))
)).done([=](const MTPmessages_Messages &rootResult) {
Expects(_topicProcess != nullptr);
auto rootSlice = rootResult.match([&](
const MTPDmessages_messagesNotModified &) {
return Data::MessagesSlice();
}, [&](const auto &data) {
return Data::ParseMessagesSlice(
_topicProcess->context,
data.vmessages(),
data.vusers(),
data.vchats(),
_topicProcess->relativePath);
});
auto rootSlicePtr = std::make_shared<Data::MessagesSlice>(
std::move(rootSlice));
requestTopicReplies(
0,
0,
kMessagesSliceLimit,
[=](const MTPmessages_Messages &result) {
Expects(_topicProcess != nullptr);
const auto count = result.match(
[](const MTPDmessages_messages &data) {
return int(data.vmessages().v.size());
}, [](const MTPDmessages_messagesSlice &data) {
return data.vcount().v;
}, [](const MTPDmessages_channelMessages &data) {
return data.vcount().v;
}, [](const MTPDmessages_messagesNotModified &data) {
return -1;
});
if (count < 0) {
error("Unexpected messagesNotModified received.");
return;
}
_topicProcess->totalCount = count;
if (!_topicProcess->start(count)) {
return;
}
if (!rootSlicePtr->list.empty()) {
collectMessagesCustomEmoji(*rootSlicePtr);
_topicProcess->slice = std::move(*rootSlicePtr);
_topicProcess->fileIndex = 0;
resolveTopicCustomEmoji();
return;
}
requestTopicMessagesSlice();
});
}).send();
}
void ApiWrap::requestTopicMessagesSlice() {
Expects(_topicProcess != nullptr);
const auto offsetId = (_topicProcess->offsetId == 0)
? 1
: (_topicProcess->offsetId + 1);
requestTopicReplies(
offsetId,
-kMessagesSliceLimit,
kMessagesSliceLimit,
[=](const MTPmessages_Messages &result) {
Expects(_topicProcess != nullptr);
result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
}, [&](const auto &data) {
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
_topicProcess->lastSlice = true;
}
auto slice = Data::ParseMessagesSlice(
_topicProcess->context,
data.vmessages(),
data.vusers(),
data.vchats(),
_topicProcess->relativePath);
if (slice.list.empty()) {
_topicProcess->lastSlice = true;
}
loadTopicMessagesFiles(std::move(slice));
});
});
}
void ApiWrap::requestTopicReplies(
int offsetId,
int addOffset,
int limit,
FnMut<void(MTPmessages_Messages&&)> done) {
Expects(_topicProcess != nullptr);
_topicProcess->requestDone = std::move(done);
const auto doneHandler = [=](MTPmessages_Messages &&result) {
Expects(_topicProcess != nullptr);
base::take(_topicProcess->requestDone)(std::move(result));
};
mainRequest(MTPmessages_GetReplies(
_topicProcess->inputPeer,
MTP_int(_topicProcess->topicRootId),
MTP_int(offsetId),
MTP_int(0),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(0),
MTP_int(0),
MTP_long(0)
)).done(doneHandler).send();
}
void ApiWrap::loadTopicMessagesFiles(Data::MessagesSlice &&slice) {
Expects(_topicProcess != nullptr);
Expects(!_topicProcess->slice.has_value());
collectMessagesCustomEmoji(slice);
if (slice.list.empty()) {
_topicProcess->lastSlice = true;
}
_topicProcess->slice = std::move(slice);
_topicProcess->fileIndex = 0;
resolveTopicCustomEmoji();
}
void ApiWrap::resolveTopicCustomEmoji() {
if (_unresolvedCustomEmoji.empty()) {
loadNextTopicMessageFile();
return;
}
const auto count = std::min(
int(_unresolvedCustomEmoji.size()),
kMaxEmojiPerRequest);
auto v = QVector<MTPlong>();
v.reserve(count);
const auto till = end(_unresolvedCustomEmoji);
const auto from = end(_unresolvedCustomEmoji) - count;
for (auto i = from; i != till; ++i) {
v.push_back(MTP_long(*i));
}
_unresolvedCustomEmoji.erase(from, till);
const auto finalize = [=] {
for (const auto &id : v) {
if (_resolvedCustomEmoji.contains(id.v)) {
continue;
}
_resolvedCustomEmoji.emplace(
id.v,
Data::Document{
.file = {
.skipReason = Data::File::SkipReason::Unavailable,
},
});
}
resolveTopicCustomEmoji();
};
mainRequest(MTPmessages_GetCustomEmojiDocuments(
MTP_vector<MTPlong>(v)
)).fail([=](const MTP::Error &error) {
LOG(("Export Error: Failed to get documents for emoji."));
finalize();
return true;
}).done([=](const MTPVector<MTPDocument> &result) {
for (const auto &entry : result.v) {
auto document = Data::ParseDocument(
_topicProcess->context,
entry,
_topicProcess->relativePath,
TimeId());
_resolvedCustomEmoji.emplace(document.id, std::move(document));
}
finalize();
}).send();
}
void ApiWrap::loadNextTopicMessageFile() {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
const auto makeProgress = [=](FileProgress progress) {
return _topicProcess->fileProgress(DownloadProgress{
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _topicProcess->fileIndex,
.ready = progress.ready,
.total = progress.total,
});
};
for (auto &list = _topicProcess->slice->list
; _topicProcess->fileIndex < list.size()
; ++_topicProcess->fileIndex) {
auto &message = list[_topicProcess->fileIndex];
if (!messageCustomEmojiReady(message)) {
return;
}
const auto origin = Data::FileOrigin{
.peer = _topicProcess->inputPeer,
.messageId = message.id
};
const auto ready = processFileLoad(
message.file(),
origin,
makeProgress,
[=, &message](const QString &path) {
loadTopicMessageFileOrThumbDone(message.file(), path);
},
&message);
if (!ready) {
return;
}
const auto thumbReady = processFileLoad(
message.thumb().file,
origin,
makeProgress,
[=, &message](const QString &path) {
loadTopicMessageFileOrThumbDone(message.thumb().file, path);
},
&message);
if (!thumbReady) {
return;
}
}
finishTopicMessagesSlice();
}
void ApiWrap::finishTopicMessagesSlice() {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
auto slice = *base::take(_topicProcess->slice);
if (!slice.list.empty()) {
_topicProcess->offsetId = slice.list.back().id;
_topicProcess->processedCount += slice.list.size();
if (!_topicProcess->handleSlice(std::move(slice))) {
return;
}
}
const auto reachedTotal = _topicProcess->totalCount > 0
&& _topicProcess->processedCount >= _topicProcess->totalCount;
if (!_topicProcess->lastSlice && !reachedTotal) {
requestTopicMessagesSlice();
} else {
finishTopicMessages();
}
}
void ApiWrap::loadTopicMessageFileOrThumbDone(
Data::File &file,
const QString &relativePath) {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
Expects((_topicProcess->fileIndex >= 0)
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextTopicMessageFile();
}
void ApiWrap::finishTopicMessages() {
Expects(_topicProcess != nullptr);
Expects(!_topicProcess->slice.has_value());
const auto process = base::take(_topicProcess);
process->done();
}
bool ApiWrap::processFileLoad(
Data::File &file,
const Data::FileOrigin &origin,

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);
@@ -97,6 +106,15 @@ public:
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done);
void requestTopicMessages(
PeerId peerId,
MTPInputPeer inputPeer,
int32 topicRootId,
FnMut<bool(int count)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done);
void finishExport(FnMut<void()> done);
void skipFile(uint64 randomId);
void cancelExportFast();
@@ -109,18 +127,22 @@ private:
struct ContactsProcess;
struct UserpicsProcess;
struct StoriesProcess;
struct ProfileMusicProcess;
struct OtherDataProcess;
struct FileProcess;
struct FileProgress;
struct ChatsProcess;
struct LeftChannelsProcess;
struct DialogsProcess;
struct AbstractMessagesProcess;
struct ChatProcess;
struct TopicProcess;
void startMainSession(FnMut<void()> done);
void sendNextStartRequest();
void requestUserpicsCount();
void requestStoriesCount();
void requestProfileMusicCount();
void requestSplitRanges();
void requestDialogsCount();
void requestLeftChannelsCount();
@@ -146,6 +168,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;
@@ -181,6 +213,12 @@ private:
int addOffset,
int limit,
FnMut<void(MTPmessages_Messages&&)> done);
void requestTopicMessagesSlice();
void requestTopicReplies(
int offsetId,
int addOffset,
int limit,
FnMut<void(MTPmessages_Messages&&)> done);
void collectMessagesCustomEmoji(const Data::MessagesSlice &slice);
void resolveCustomEmoji();
void loadMessagesFiles(Data::MessagesSlice &&slice);
@@ -196,6 +234,17 @@ private:
void finishMessagesSlice();
void finishMessages();
void loadTopicMessagesFiles(Data::MessagesSlice &&slice);
void resolveTopicCustomEmoji();
void loadNextTopicMessageFile();
bool loadTopicEmojiProgress(FileProgress progress);
void loadCustomEmojiDone(uint64 id, const QString &relativePath);
void loadTopicMessageFileOrThumbDone(
Data::File &file,
const QString &relativePath);
void finishTopicMessagesSlice();
void finishTopicMessages();
[[nodiscard]] Data::Message *currentFileMessage() const;
[[nodiscard]] Data::FileOrigin currentFileMessageOrigin() const;
@@ -258,11 +307,13 @@ 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;
std::unique_ptr<DialogsProcess> _dialogsProcess;
std::unique_ptr<ChatProcess> _chatProcess;
std::unique_ptr<TopicProcess> _topicProcess;
base::flat_set<uint64> _unresolvedCustomEmoji;
base::flat_map<uint64, Data::Document> _resolvedCustomEmoji;
QVector<MTPMessageRange> _splits;

View File

@@ -37,6 +37,13 @@ public:
crl::weak_on_queue<ControllerObject> weak,
QPointer<MTP::Instance> mtproto,
const MTPInputPeer &peer);
ControllerObject(
crl::weak_on_queue<ControllerObject> weak,
QPointer<MTP::Instance> mtproto,
const MTPInputPeer &peer,
int32 topicRootId,
uint64 peerId,
const QString &topicTitle);
rpl::producer<State> state() const;
@@ -76,11 +83,13 @@ private:
void exportPersonalInfo();
void exportUserpics();
void exportStories();
void exportProfileMusic();
void exportContacts();
void exportSessions();
void exportOtherData();
void exportDialogs();
void exportNextDialog();
void exportTopic();
template <typename Callback = const decltype(kNullStateCallback) &>
ProcessingState prepareState(
@@ -91,10 +100,12 @@ 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;
ProcessingState stateDialogs(const DownloadProgress &progress) const;
ProcessingState stateTopic(const DownloadProgress &progress) const;
void fillMessagesState(
ProcessingState &result,
const Data::DialogsInfo &info,
@@ -119,6 +130,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;
@@ -134,6 +148,10 @@ private:
std::vector<Step> _steps;
int _stepIndex = -1;
int32 _topicRootId = 0;
uint64 _topicPeerId = 0;
QString _topicTitle;
rpl::lifetime _lifetime;
};
@@ -162,6 +180,36 @@ ControllerObject::ControllerObject(
setState(std::move(state));
}
ControllerObject::ControllerObject(
crl::weak_on_queue<ControllerObject> weak,
QPointer<MTP::Instance> mtproto,
const MTPInputPeer &peer,
int32 topicRootId,
uint64 peerId,
const QString &topicTitle)
: _api(mtproto, weak.runner())
, _state(PasswordCheckState{})
, _topicRootId(topicRootId)
, _topicPeerId(peerId)
, _topicTitle(topicTitle) {
_api.errors(
) | rpl::start_with_next([=](const MTP::Error &error) {
setState(ApiErrorState{ error });
}, _lifetime);
_api.ioErrors(
) | rpl::start_with_next([=](const Output::Result &result) {
ioCatchError(result);
}, _lifetime);
//requestPasswordState();
auto state = PasswordCheckState();
state.checked = false;
state.requesting = false;
state.singlePeer = peer;
setState(std::move(state));
}
rpl::producer<State> ControllerObject::state() const {
return rpl::single(
_state
@@ -252,6 +300,8 @@ void ControllerObject::startExport(
}
_settings = NormalizeSettings(settings);
_environment = environment;
_settings.singleTopicRootId = _topicRootId;
_settings.singleTopicPeerId = _topicPeerId;
_settings.path = Output::NormalizePath(_settings);
_writer = Output::CreateWriter(_settings.format);
@@ -269,6 +319,10 @@ void ControllerObject::skipFile(uint64 randomId) {
void ControllerObject::fillExportSteps() {
using Type = Settings::Type;
_steps.push_back(Step::Initializing);
if (_settings.onlySingleTopic()) {
_steps.push_back(Step::Topic);
return;
}
if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::DialogsList);
}
@@ -281,6 +335,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 +374,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);
}
@@ -329,6 +389,9 @@ void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::Dialogs, info.dialogsCount);
}
if (_settings.onlySingleTopic()) {
push(Step::Topic, 1);
}
_substepsInStep = std::move(result);
_substepsTotal = ranges::accumulate(_substepsInStep, 0);
}
@@ -356,10 +419,12 @@ 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();
case Step::Dialogs: return exportDialogs();
case Step::Topic: return exportTopic();
}
Unexpected("Step in ControllerObject::exportNext.");
}
@@ -454,6 +519,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 +687,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);
}
@@ -655,6 +761,71 @@ int ControllerObject::substepsInStep(Step step) const {
return _substepsInStep[static_cast<int>(step)];
}
void ControllerObject::exportTopic() {
auto topicInfo = Data::DialogInfo();
topicInfo.type = Data::DialogInfo::Type::PublicSupergroup;
topicInfo.name = _topicTitle.toUtf8();
topicInfo.peerId = PeerId(_topicPeerId);
topicInfo.relativePath = QString();
if (ioCatchError(_writer->writeDialogStart(topicInfo))) {
return;
}
_api.requestTopicMessages(
PeerId(_topicPeerId),
_settings.singlePeer,
_topicRootId,
[=](int count) {
_messagesWritten = 0;
_messagesCount = count;
setState(stateTopic(DownloadProgress()));
return true;
},
[=](DownloadProgress progress) {
setState(stateTopic(progress));
return true;
},
[=](Data::MessagesSlice &&slice) {
if (ioCatchError(_writer->writeDialogSlice(slice))) {
return false;
}
_messagesWritten += slice.list.size();
setState(stateTopic(DownloadProgress()));
return true;
},
[=] {
if (ioCatchError(_writer->writeDialogEnd())) {
return;
}
if (ioCatchError(_writer->finish())) {
return;
}
_api.finishExport([=] {
setFinishedState();
});
});
}
ProcessingState ControllerObject::stateTopic(
const DownloadProgress &progress) const {
return prepareState(Step::Topic, [&](ProcessingState &result) {
result.entityType = ProcessingState::EntityType::Topic;
result.entityName = _topicTitle;
result.entityIndex = 0;
result.entityCount = 1;
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.itemIndex);
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;
});
}
void ControllerObject::setFinishedState() {
setState(FinishedState{
_writer->mainFilePath(),
@@ -668,6 +839,20 @@ Controller::Controller(
: _wrapped(std::move(mtproto), peer) {
}
Controller::Controller(
QPointer<MTP::Instance> mtproto,
const MTPInputPeer &peer,
int32 topicRootId,
uint64 peerId,
const QString &topicTitle)
: _wrapped(
std::move(mtproto),
peer,
static_cast<int32>(topicRootId),
static_cast<uint64>(peerId),
topicTitle) {
}
rpl::producer<State> Controller::state() const {
return _wrapped.producer_on_main([=](const Implementation &unwrapped) {
return unwrapped.state();

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