Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e75a41ee6 | ||
|
|
84266aef2c | ||
|
|
40a7f8ea50 | ||
|
|
7c4fcdd9cb | ||
|
|
92e87852c1 | ||
|
|
73c4da2b21 | ||
|
|
9f4da7e890 | ||
|
|
8b23457373 | ||
|
|
ab2fd7c749 | ||
|
|
040a6ddf3a | ||
|
|
55afe0912f | ||
|
|
ee48127094 | ||
|
|
445576d568 | ||
|
|
8f1d40892e | ||
|
|
766db9660c | ||
|
|
68665ec1f2 | ||
|
|
b400964aa1 | ||
|
|
317530cfa3 | ||
|
|
e8fba23b59 | ||
|
|
ef749e695e | ||
|
|
712ef33d6b | ||
|
|
0441b7dbc3 | ||
|
|
3ee0dcbacd | ||
|
|
57411b962f | ||
|
|
4eee00d95e | ||
|
|
957a08962f | ||
|
|
21c82f5fe1 | ||
|
|
27964993f6 | ||
|
|
14e296e1f9 | ||
|
|
23c0ff934f | ||
|
|
c64ef1e20e | ||
|
|
ed97619c6c | ||
|
|
924ec592b1 | ||
|
|
669c581701 | ||
|
|
e97ae3d537 | ||
|
|
08800b68f4 | ||
|
|
a59db6529c | ||
|
|
02a54ceea6 | ||
|
|
f0a7c547e8 | ||
|
|
ecfb343690 | ||
|
|
c65472c9b3 | ||
|
|
c42864d35e | ||
|
|
b02ce599e6 | ||
|
|
1ae5495b91 | ||
|
|
c4fbb8c199 | ||
|
|
313872dacc | ||
|
|
ef15136a3b | ||
|
|
4342c8d761 | ||
|
|
644744ac9e | ||
|
|
cbc03d1e45 | ||
|
|
7f56192b97 | ||
|
|
6590f3b741 | ||
|
|
c0bbb669e0 | ||
|
|
63014adfef | ||
|
|
1ad055c8c8 | ||
|
|
9b558564e9 | ||
|
|
476e66d027 | ||
|
|
fc11d81673 | ||
|
|
629754a353 | ||
|
|
147dbee051 | ||
|
|
7204c3c25d | ||
|
|
b412241d25 | ||
|
|
f9883afd61 | ||
|
|
181f811f18 | ||
|
|
2f0bd3c085 | ||
|
|
a9d8332766 | ||
|
|
f5036171cf | ||
|
|
cdc8b8e473 | ||
|
|
ab404c5452 | ||
|
|
0585f9d667 | ||
|
|
19225c7dd3 | ||
|
|
8c1844b1c0 | ||
|
|
af63d86e24 | ||
|
|
1f2fd3ad96 | ||
|
|
f41fdcdb98 | ||
|
|
f78a9b4220 | ||
|
|
06b3ce58ed | ||
|
|
7c70d8b1c2 | ||
|
|
2b1e032a9b | ||
|
|
4f5d6a2fd5 | ||
|
|
c3b90aa492 | ||
|
|
bb2daac007 | ||
|
|
bc2449c3f9 | ||
|
|
0bf50de77a | ||
|
|
473bc32b71 | ||
|
|
e5e143dcf8 | ||
|
|
2de08746ac | ||
|
|
ae6833b4d5 | ||
|
|
f1fe5f6a71 | ||
|
|
5054d0615e | ||
|
|
4f5007ea64 | ||
|
|
233f6ed13b | ||
|
|
f15b883471 | ||
|
|
312d5f0121 | ||
|
|
75a1657c49 | ||
|
|
667d92100e | ||
|
|
8ff4bc8cff | ||
|
|
656b262648 | ||
|
|
c1769b9ba2 | ||
|
|
04c9d92b4a | ||
|
|
0babef5a09 | ||
|
|
cd52407723 | ||
|
|
68c0aa7fb9 | ||
|
|
f1622c40a4 | ||
|
|
688e7316eb | ||
|
|
ef5ec47797 | ||
|
|
147ad4a773 | ||
|
|
9752145b49 | ||
|
|
662b862d2f | ||
|
|
6d469509a4 | ||
|
|
0dfbb8a5ae | ||
|
|
1e12ecda70 | ||
|
|
035087987c | ||
|
|
18422c4193 | ||
|
|
f1f9fe27a9 | ||
|
|
10667e14e2 | ||
|
|
b04c7efdf4 | ||
|
|
0119731360 | ||
|
|
8f337684d5 | ||
|
|
dae93552f0 | ||
|
|
351bbb240f | ||
|
|
5716de2e6e | ||
|
|
14295d59d1 | ||
|
|
df0849473c | ||
|
|
9736706894 | ||
|
|
12343a5c31 | ||
|
|
a7f046a617 | ||
|
|
67bf796f1e | ||
|
|
22d632abc3 | ||
|
|
25094c1ee6 | ||
|
|
5b71ad0456 | ||
|
|
9146ba996f | ||
|
|
42900787e1 | ||
|
|
ba10c10a94 | ||
|
|
3f37e9ca6f | ||
|
|
c5ea86b474 | ||
|
|
76720092a5 | ||
|
|
7a75c80b27 | ||
|
|
b2dcbebb5b |
2
.github/workflows/docker.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
4
.github/workflows/mac.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/mac_packaged.yml
vendored
@@ -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 }}
|
||||
|
||||
4
.github/workflows/snap.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/win.yml
vendored
@@ -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 }}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 935 KiB After Width: | Height: | Size: 938 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 250 KiB |
@@ -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 */
|
||||
}
|
||||
}
|
||||
BIN
Telegram/Resources/export_html/images/section_music.png
Normal file
|
After Width: | Height: | Size: 446 B |
BIN
Telegram/Resources/export_html/images/section_music@2x.png
Normal file
|
After Width: | Height: | Size: 777 B |
BIN
Telegram/Resources/icons/chat/mini_gift_hidden.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
Telegram/Resources/icons/chat/mini_gift_hidden@2x.png
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
Telegram/Resources/icons/chat/mini_gift_hidden@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/settings/button_auction.png
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
Telegram/Resources/icons/settings/button_auction@2x.png
Normal file
|
After Width: | Height: | Size: 935 B |
BIN
Telegram/Resources/icons/settings/button_auction@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction.png
Normal file
|
After Width: | Height: | Size: 605 B |
BIN
Telegram/Resources/icons/settings/toast_auction@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -378,10 +378,8 @@ connectionPortInputField: InputField(defaultInputField) {
|
||||
width: 55px;
|
||||
}
|
||||
connectionUserInputField: InputField(defaultInputField) {
|
||||
width: 95px;
|
||||
}
|
||||
connectionPasswordInputField: InputField(defaultInputField) {
|
||||
width: 120px;
|
||||
}
|
||||
connectionIPv6Skip: 11px;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ private:
|
||||
Ui::RoundRect pinnedLight;
|
||||
Ui::RoundRect pinnedDark;
|
||||
Ui::RoundRect messageLight;
|
||||
Ui::RoundRect priceDark;
|
||||
Ui::RoundRect badgeDark;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ struct VideoStreamStarsBoxArgs {
|
||||
int min = 0;
|
||||
int current = 0;
|
||||
bool sending = false;
|
||||
bool admin = false;
|
||||
Fn<void(int)> save;
|
||||
QString name;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 *) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||