Compare commits
285 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 | ||
|
|
aadc81279a | ||
|
|
febd6dd18f | ||
|
|
1236b35aaf | ||
|
|
41a9a25823 | ||
|
|
55ae81524d | ||
|
|
a9cf5dd6f2 | ||
|
|
1e45692ba4 | ||
|
|
997cff63a8 | ||
|
|
5db513cc55 | ||
|
|
4a7280be5e | ||
|
|
59826d492f | ||
|
|
85c2a5fd75 | ||
|
|
33dd3342e8 | ||
|
|
94fc50f793 | ||
|
|
1e79f4a9a5 | ||
|
|
f56825c526 | ||
|
|
d7c0f9dee8 | ||
|
|
a71c59beae | ||
|
|
5f729433f6 | ||
|
|
a1d37475c9 | ||
|
|
9275dfce70 | ||
|
|
ff9afdae3e | ||
|
|
12a2dcf484 | ||
|
|
c3f5f69c2a | ||
|
|
1320e7ac2f | ||
|
|
ae6b0dd753 | ||
|
|
e43830c08e | ||
|
|
4a5cdbcfb2 | ||
|
|
9b599644d9 | ||
|
|
cab93600dd | ||
|
|
0e5e4ca7ea | ||
|
|
a293134b5e | ||
|
|
3831860943 | ||
|
|
1f8214e658 | ||
|
|
58d2e2ece2 | ||
|
|
424f416fdd | ||
|
|
95c12a0b8e | ||
|
|
45d42c8c31 | ||
|
|
5b94095c78 | ||
|
|
605ad0a01c | ||
|
|
10b8dc3595 | ||
|
|
432f6aeae6 | ||
|
|
6702ff3c55 | ||
|
|
8f205f13d8 | ||
|
|
22a133b182 | ||
|
|
bc61175e79 | ||
|
|
874e344f91 | ||
|
|
f832e31c7b | ||
|
|
4cdd793e0c | ||
|
|
0d11cb603f | ||
|
|
1e197ae66c | ||
|
|
2a88103b5f | ||
|
|
49caea416b | ||
|
|
5bfe270f24 | ||
|
|
e8f10f2b45 | ||
|
|
834986410c | ||
|
|
deffbcf231 | ||
|
|
2d720a8349 | ||
|
|
182d45b7ea | ||
|
|
3cd68842bf | ||
|
|
861ada351e | ||
|
|
e32d863daa | ||
|
|
81fc652dc1 | ||
|
|
b72deb1a0e | ||
|
|
cab38f00c2 | ||
|
|
137594ccee | ||
|
|
449f7fb2a3 | ||
|
|
2a5bea7e4e | ||
|
|
ea309caa22 | ||
|
|
89bc58ab29 | ||
|
|
2d754c93a7 | ||
|
|
1017d4cda3 | ||
|
|
68dab26be5 | ||
|
|
8eb0ec9f7a | ||
|
|
1da47f62fc | ||
|
|
70939c4b9c | ||
|
|
491ad744ea | ||
|
|
a1565c7fff | ||
|
|
496dbfb2f0 | ||
|
|
0e44dac208 | ||
|
|
2b808933d2 | ||
|
|
ba0f682e3a | ||
|
|
ef49652415 | ||
|
|
8e30ee1192 | ||
|
|
74a676026f | ||
|
|
af93c2ee49 | ||
|
|
6b05d253a7 | ||
|
|
797b2a5085 | ||
|
|
b898cf4fde | ||
|
|
1c17432f70 | ||
|
|
a6c96df51f | ||
|
|
d7abe73753 | ||
|
|
1b84063dd3 | ||
|
|
ed5b7fe3e6 | ||
|
|
699ac83729 | ||
|
|
1e1ce492e6 | ||
|
|
7afb9f1fc8 | ||
|
|
7bfe418c3e | ||
|
|
94c1388b6a | ||
|
|
90f53cba31 | ||
|
|
a675fca607 | ||
|
|
bf58171f64 | ||
|
|
e619bd4acc | ||
|
|
5f5b7ffb66 | ||
|
|
2216e75cab | ||
|
|
a61d73f48a | ||
|
|
ecdca38d9e | ||
|
|
a34bc7cc89 | ||
|
|
d12a0be66c | ||
|
|
890421e00e | ||
|
|
dc57886b68 | ||
|
|
832fb023e8 | ||
|
|
af8c171b1a | ||
|
|
58264f8b57 | ||
|
|
eb7d614566 | ||
|
|
c11cab858d | ||
|
|
18438b17a5 | ||
|
|
f2464df96f | ||
|
|
5ee0a2fea2 | ||
|
|
1296505d01 | ||
|
|
332b70a27d | ||
|
|
c4d5d52b96 | ||
|
|
7a5554202e | ||
|
|
df2c426096 | ||
|
|
b33cdc581b | ||
|
|
3cd213b9bf | ||
|
|
aebb37b516 | ||
|
|
d016c80ba5 | ||
|
|
882f1c4d1a | ||
|
|
3ffdf8d281 | ||
|
|
c183243711 | ||
|
|
eeb2c953ad | ||
|
|
bfb2f3015a | ||
|
|
9c54f53613 | ||
|
|
257c7af2f3 | ||
|
|
4ed256814a | ||
|
|
1f8ea32388 | ||
|
|
69403e7967 | ||
|
|
7cc4c76c54 | ||
|
|
cf74a81d07 | ||
|
|
417d151f2c | ||
|
|
ff42b7f61a | ||
|
|
658c2a0a78 | ||
|
|
90d6899577 | ||
|
|
5db0e53c0c | ||
|
|
7dea4f36fb |
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 }}
|
||||
|
||||
@@ -333,8 +333,12 @@ PRIVATE
|
||||
boxes/send_files_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_auction_box.cpp
|
||||
boxes/star_gift_auction_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/star_gift_resale_box.cpp
|
||||
boxes/star_gift_resale_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
@@ -377,6 +381,8 @@ PRIVATE
|
||||
calls/group/calls_group_rtmp.h
|
||||
calls/group/calls_group_settings.cpp
|
||||
calls/group/calls_group_settings.h
|
||||
calls/group/calls_group_stars_box.cpp
|
||||
calls/group/calls_group_stars_box.h
|
||||
calls/group/calls_group_toasts.cpp
|
||||
calls/group/calls_group_toasts.h
|
||||
calls/group/calls_group_viewport.cpp
|
||||
@@ -513,6 +519,8 @@ PRIVATE
|
||||
data/components/credits.h
|
||||
data/components/factchecks.cpp
|
||||
data/components/factchecks.h
|
||||
data/components/gift_auctions.cpp
|
||||
data/components/gift_auctions.h
|
||||
data/components/location_pickers.cpp
|
||||
data/components/location_pickers.h
|
||||
data/components/promo_suggestions.cpp
|
||||
@@ -1302,6 +1310,8 @@ PRIVATE
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_playback_sponsored.cpp
|
||||
media/view/media_view_playback_sponsored.h
|
||||
media/view/media_view_video_stream.cpp
|
||||
media/view/media_view_video_stream.h
|
||||
media/system_media_controls_manager.h
|
||||
media/system_media_controls_manager.cpp
|
||||
menu/menu_antispam_validator.cpp
|
||||
@@ -1636,6 +1646,8 @@ PRIVATE
|
||||
ui/controls/location_picker.h
|
||||
ui/controls/silent_toggle.cpp
|
||||
ui/controls/silent_toggle.h
|
||||
ui/controls/table_rows.cpp
|
||||
ui/controls/table_rows.h
|
||||
ui/controls/userpic_button.cpp
|
||||
ui/controls/userpic_button.h
|
||||
ui/effects/credits_graphics.cpp
|
||||
@@ -1721,6 +1733,8 @@ PRIVATE
|
||||
window/window_session_controller.cpp
|
||||
window/window_session_controller.h
|
||||
window/window_session_controller_link_info.h
|
||||
window/window_setup_email.cpp
|
||||
window/window_setup_email.h
|
||||
window/window_top_bar_wrap.h
|
||||
window/themes/window_theme.cpp
|
||||
window/themes/window_theme.h
|
||||
|
||||
|
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 |
7
Telegram/Resources/icons/calls/filled_stream_crown.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_stream_crown</title>
|
||||
<g id="Filled-/-filled_stream_crown" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M10.5793798,21.3515408 L22.4461242,27.5065085 C23.3919447,27.6775566 24.3643369,27.4213643 25.1079413,26.8052098 L34.6112236,12.5021245 C35.4192073,11.8326252 36.5811617,11.8326252 37.3891453,12.5021245 L46.8924276,26.8052098 C47.6360321,27.4213643 48.6084242,27.6775566 49.5542448,27.5065085 L61.4209891,21.3515408 C62.613521,21.1358757 63.7528909,21.9400986 63.965843,23.1478228 C64.0112154,23.4051449 64.0113863,23.6685576 63.9663481,23.9259399 L57.0245486,49.6650027 C56.2820716,53.9080693 48.2297454,57 36.0001845,57 C23.7706236,57 15.7182973,53.9080693 14.9758203,49.6650027 L8.03402089,23.9259399 C7.8226367,22.7179332 8.61824017,21.5651064 9.81105098,21.3510293 C10.0651955,21.3054173 10.3252947,21.3055904 10.5793798,21.3515408 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/chat/input_comments_expand.png
Normal file
|
After Width: | Height: | Size: 833 B |
BIN
Telegram/Resources/icons/chat/input_comments_expand@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/chat/input_comments_expand@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/chat/input_comments_hide.png
Normal file
|
After Width: | Height: | Size: 839 B |
BIN
Telegram/Resources/icons/chat/input_comments_hide@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/chat/input_comments_hide@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/chat/input_send_round.png
Normal file
|
After Width: | Height: | Size: 514 B |
BIN
Telegram/Resources/icons/chat/input_send_round@2x.png
Normal file
|
After Width: | Height: | Size: 1012 B |
BIN
Telegram/Resources/icons/chat/input_send_round@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
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/menu/auction_carry.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
Telegram/Resources/icons/menu/auction_carry@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/menu/auction_carry@3x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Telegram/Resources/icons/menu/auction_drop.png
Normal file
|
After Width: | Height: | Size: 824 B |
BIN
Telegram/Resources/icons/menu/auction_drop@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/auction_drop@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/auction_refund.png
Normal file
|
After Width: | Height: | Size: 989 B |
BIN
Telegram/Resources/icons/menu/auction_refund@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/menu/auction_refund@3x.png
Normal file
|
After Width: | Height: | Size: 2.8 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/large_auctions.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/large_auctions@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Telegram/Resources/icons/settings/large_auctions@3x.png
Normal file
|
After Width: | Height: | Size: 3.1 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.";
|
||||
@@ -759,6 +760,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_bio_privacy" = "Bio";
|
||||
"lng_settings_gifts_privacy" = "Gifts";
|
||||
"lng_settings_birthday_privacy" = "Date of Birth";
|
||||
"lng_settings_saved_music_privacy" = "Saved Music";
|
||||
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
"lng_settings_passcode_disable" = "Disable passcode";
|
||||
@@ -929,6 +931,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_cloud_login_email_code_title" = "Check Your New Email";
|
||||
"lng_settings_cloud_login_email_code_about" = "Please enter the code we have sent to your new email {email}";
|
||||
"lng_settings_cloud_login_email_success" = "Your email has been changed.";
|
||||
"lng_settings_cloud_login_email_set_success" = "Your login email has been set successfully.";
|
||||
"lng_settings_cloud_login_email_busy" = "Please set up login email in another window.";
|
||||
"lng_settings_error_email_not_alowed" = "Sorry, this email is not allowed";
|
||||
|
||||
"lng_settings_ttl_title" = "Auto-Delete Messages";
|
||||
@@ -998,6 +1002,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_color_changed_profile" = "Your profile style has been updated!";
|
||||
"lng_settings_color_changed_profile_channel" = "Your channel profile style has been updated!";
|
||||
"lng_settings_color_apply" = "Apply Style";
|
||||
"lng_settings_color_wear" = "Wear Collectible";
|
||||
"lng_settings_color_profile_emoji" = "Add icons to Profile";
|
||||
"lng_settings_color_profile_emoji_channel" = "Profile Logo";
|
||||
"lng_settings_color_reset" = "Reset Profile Color";
|
||||
@@ -1406,6 +1411,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_privacy_gifts_unlimited" = "Unlimited";
|
||||
"lng_edit_privacy_gifts_limited" = "Limited-Edition";
|
||||
"lng_edit_privacy_gifts_unique" = "Unique";
|
||||
"lng_edit_privacy_gifts_channels" = "From Channels";
|
||||
"lng_edit_privacy_gifts_types_about" = "Choose the types of gifts that you accept.";
|
||||
"lng_edit_privacy_gifts_show_icon" = "Show Gift Icon in Chats";
|
||||
"lng_edit_privacy_gifts_show_icon_about" = "Display the {emoji}Gift icon in the message input field for both participants in all chats.";
|
||||
@@ -1465,6 +1471,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_privacy_voices_always_title" = "Always allow";
|
||||
"lng_edit_privacy_voices_never_title" = "Never Allow";
|
||||
|
||||
"lng_edit_privacy_saved_music_title" = "Saved Music";
|
||||
"lng_edit_privacy_saved_music_header" = "Who can see my saved music in profile";
|
||||
"lng_edit_privacy_saved_music_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_saved_music_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_saved_music_exceptions" = "These users will or will not be able to see your saved music regardless of the settings above.";
|
||||
"lng_edit_privacy_saved_music_always_title" = "Always allow";
|
||||
"lng_edit_privacy_saved_music_never_title" = "Never allow";
|
||||
|
||||
"lng_messages_privacy_title" = "Messages";
|
||||
"lng_messages_privacy_subtitle" = "Who can send me messages";
|
||||
"lng_messages_privacy_everyone" = "Everybody";
|
||||
@@ -1578,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.";
|
||||
@@ -1669,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.";
|
||||
@@ -1686,6 +1704,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_changed_photo_link" = "Settings";
|
||||
|
||||
"lng_profile_action_short_message" = "Message";
|
||||
"lng_profile_action_short_channel" = "Channel";
|
||||
"lng_profile_action_short_mute" = "Mute";
|
||||
"lng_profile_action_short_unmute" = "Unmute";
|
||||
"lng_profile_action_short_call" = "Call";
|
||||
@@ -1716,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";
|
||||
@@ -2275,6 +2295,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
|
||||
"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_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.";
|
||||
@@ -2430,6 +2452,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_peer_gifts_filter_saved" = "Displayed";
|
||||
"lng_peer_gifts_filter_unsaved" = "Hidden";
|
||||
|
||||
"lng_premium_gift_duration_days#one" = "for {count} day";
|
||||
"lng_premium_gift_duration_days#other" = "for {count} days";
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
"lng_premium_gift_duration_years#one" = "for {count} year";
|
||||
@@ -2607,6 +2631,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edited" = "edited";
|
||||
"lng_commented" = "commented";
|
||||
"lng_approximate" = "appx.";
|
||||
"lng_repeated_daily" = "daily";
|
||||
"lng_repeated_weekly" = "weekly";
|
||||
"lng_repeated_biweekly" = "biweekly";
|
||||
"lng_repeated_monthly" = "monthly";
|
||||
"lng_repeated_every_month#one" = "{count}-monthly";
|
||||
"lng_repeated_every_month#other" = "{count}-monthly";
|
||||
"lng_repeated_yearly" = "yearly";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_approximate_about" = "Estimated date of video publishing.";
|
||||
@@ -2725,8 +2756,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_title_subscribed" = "You are all set!";
|
||||
"lng_premium_summary_subtitle_gift#one" = "{user} has gifted you a {count}-month subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift#other" = "{user} has gifted you a {count}-months subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_days#one" = "{user} has gifted you a {count}-day subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_days#other" = "{user} has gifted you a {count}-days subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_me#one" = "You gifted {user} a {count}-month subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_me#other" = "You gifted {user} a {count}-months subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_days_me#one" = "You gifted {user} a {count}-month subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_gift_days_me#other" = "You gifted {user} a {count}-months subscription to Telegram Premium.";
|
||||
"lng_premium_summary_subtitle_wallpapers" = "Wallpapers for Both Sides";
|
||||
"lng_premium_summary_about_wallpapers" = "Set custom wallpapers for you and your chat partner.";
|
||||
"lng_premium_summary_subtitle_stories" = "Stories";
|
||||
@@ -2923,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}";
|
||||
@@ -3054,6 +3090,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_video_stream" = "Buy **Stars** to send them to {name} to support their stream.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
|
||||
@@ -3653,6 +3690,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_resale" = "resale";
|
||||
"lng_gift_stars_on_sale" = "on sale";
|
||||
"lng_gift_stars_premium" = "premium";
|
||||
"lng_gift_stars_auction" = "auction";
|
||||
"lng_gift_stars_auction_join" = "Join";
|
||||
"lng_gift_stars_your_left#one" = "{count} left";
|
||||
"lng_gift_stars_your_left#other" = "{count} left";
|
||||
"lng_gift_stars_your_finished" = "none left";
|
||||
@@ -3956,6 +3995,102 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_collection_remove_from" = "Remove from Collection";
|
||||
"lng_gift_locked_title" = "Gift Locked";
|
||||
|
||||
"lng_auction_about_title" = "Auction";
|
||||
"lng_auction_about_subtitle" = "Join the battle for exclusive gifts.";
|
||||
"lng_auction_about_top_title#one" = "Top {count} Bidder";
|
||||
"lng_auction_about_top_title#other" = "Top {count} Bidders";
|
||||
"lng_auction_about_top_rounds#one" = "{count} round";
|
||||
"lng_auction_about_top_rounds#other" = "{count} rounds";
|
||||
"lng_auction_about_top_bidders#one" = "top {count} bidder";
|
||||
"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.";
|
||||
"lng_auction_about_missed_title" = "Missed Bidders";
|
||||
"lng_auction_about_missed_about" = "If your bid doesn't win after the final round, your Stars will be fully refunded.";
|
||||
"lng_auction_about_understood" = "Understood";
|
||||
"lng_auction_text#one" = "Top **{count}** bidder will get {name} gifts this round. {link}";
|
||||
"lng_auction_text#other" = "Top **{count}** bidders will get {name} gifts this round. {link}";
|
||||
"lng_auction_text_link" = "Learn more {arrow}";
|
||||
"lng_auction_text_ended" = "Auction ended.";
|
||||
"lng_auction_start_label" = "Started";
|
||||
"lng_auction_end_label" = "Ends";
|
||||
"lng_auction_round_label" = "Current Round";
|
||||
"lng_auction_round_value" = "{n} of {amount}";
|
||||
"lng_auction_average_label" = "Average Price";
|
||||
"lng_auction_average_tooltip" = "{amount} is the average sale price for {gift} gifts.";
|
||||
"lng_auction_availability_label" = "Availability";
|
||||
"lng_auction_availability_value" = "{n} of {amount} left";
|
||||
"lng_auction_bought#one" = "{count} {emoji} item bought {arrow}";
|
||||
"lng_auction_bought#other" = "{count} {emoji} items bought {arrow}";
|
||||
"lng_auction_join_button" = "Join Auction";
|
||||
"lng_auction_join_bid" = "Place a Bid";
|
||||
"lng_auction_join_time_left" = "{time} left";
|
||||
"lng_auction_join_time_medium" = "{hours} h {minutes} m";
|
||||
"lng_auction_join_time_small" = "{minutes} m";
|
||||
"lng_auction_menu_about" = "About";
|
||||
"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";
|
||||
"lng_auction_bid_minimal#other" = "minimum bid";
|
||||
"lng_auction_bid_until" = "until next round";
|
||||
"lng_auction_bid_left#one" = "left";
|
||||
"lng_auction_bid_left#other" = "left";
|
||||
"lng_auction_bid_your_title" = "Your bid will be";
|
||||
"lng_auction_bid_your_outbid" = "You've been outbid";
|
||||
"lng_auction_bid_your_winning" = "You're winning";
|
||||
"lng_auction_bid_winners_title" = "Top winners";
|
||||
"lng_auction_bid_place" = "Place a {stars} Bid";
|
||||
"lng_auction_bid_increase" = "Add {stars} to Your Bid";
|
||||
"lng_auction_bid_placed_title" = "Your bid has been placed";
|
||||
"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";
|
||||
"lng_auctino_preview_finished" = "Finished";
|
||||
"lng_auction_preview_sold_out" = "Sold Out";
|
||||
"lng_auction_preview_view_results" = "View Results";
|
||||
"lng_auction_bar_active" = "Active Auction";
|
||||
"lng_auction_bar_active_many#one" = "{count} Active Auction";
|
||||
"lng_auction_bar_active_many#other" = "{count} Active Auctions";
|
||||
"lng_auction_bar_winning#one" = "You're winning ({count}st place).";
|
||||
"lng_auction_bar_winning#other" = "You're winning ({count}th place).";
|
||||
"lng_auction_bar_outbid" = "You've been outbid.";
|
||||
"lng_auction_bar_winning_all" = "You're winning in all of them.";
|
||||
"lng_auction_bar_outbid_some#one" = "You've been outbid in {count} of them.";
|
||||
"lng_auction_bar_outbid_some#other" = "You've been outbid in {count} of them.";
|
||||
"lng_auction_bar_outbid_all" = "You've been outbid in all of them.";
|
||||
"lng_auction_bar_view" = "View";
|
||||
"lng_auction_bar_round" = "Round {n} of {amount}";
|
||||
"lng_auction_bar_bid_ranked" = "Your bid **{stars}** is ranked **#{n}**";
|
||||
"lng_auction_bar_bid_outbid" = "Your bid **{stars}** is outbid";
|
||||
"lng_auction_bar_raise_bid" = "Raise Bid";
|
||||
"lng_auction_bought_title#one" = "{count} Item Bought";
|
||||
"lng_auction_bought_title#other" = "{count} Items Bought";
|
||||
"lng_auction_bought_date" = "Date";
|
||||
"lng_auction_bought_bid" = "Accepted Bid";
|
||||
"lng_auction_bought_round" = "Round #{n}";
|
||||
"lng_auction_change_title" = "Change Recipient";
|
||||
"lng_auction_change_button" = "Change";
|
||||
"lng_auction_change_already" = "You've already placed a bid on this gift for {name}.";
|
||||
"lng_auction_change_to" = "Do you want to raise your bid and change the recipient to {name}?";
|
||||
"lng_auction_change_already_me" = "You've already placed a bid on this gift for yourself.";
|
||||
"lng_auction_change_to_me" = "Do you want to raise your bid and change the recipient to yourself?";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -4136,6 +4271,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reminder_message" = "Set a reminder";
|
||||
"lng_schedule_title" = "Send this message on...";
|
||||
"lng_remind_title" = "Remind me on...";
|
||||
"lng_schedule_repeat_label" = "Repeat:";
|
||||
"lng_schedule_repeat_never" = "Never";
|
||||
"lng_schedule_repeat_daily" = "Daily";
|
||||
"lng_schedule_repeat_weekly" = "Weekly";
|
||||
"lng_schedule_repeat_biweekly" = "Biweekly";
|
||||
"lng_schedule_repeat_monthly" = "Monthly";
|
||||
"lng_schedule_repeat_every_month#one" = "Every {count} month";
|
||||
"lng_schedule_repeat_every_month#other" = "Every {count} month";
|
||||
"lng_schedule_repeat_yearly" = "Yearly";
|
||||
"lng_schedule_repeat_promo" = "Subscribe to {link} to schedule repeating messages.";
|
||||
"lng_schedule_repeat_promo_link" = "Telegram Premium";
|
||||
"lng_schedule_at" = "at";
|
||||
"lng_message_ph" = "Write a message...";
|
||||
"lng_broadcast_ph" = "Broadcast a message...";
|
||||
@@ -4143,6 +4289,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_story_reply_ph" = "Reply privately...";
|
||||
"lng_story_comment_ph" = "Comment story...";
|
||||
"lng_video_stream_comment_ph" = "Comment";
|
||||
"lng_video_stream_comment_paid_ph#one" = "Comment for {count} Star";
|
||||
"lng_video_stream_comment_paid_ph#other" = "Comment for {count} Stars";
|
||||
"lng_video_stream_comments_disabled" = "Comments disabled.";
|
||||
"lng_video_stream_stars" = "Add Stars to highlight your comment";
|
||||
"lng_video_stream_live" = "LIVE";
|
||||
"lng_video_stream_watched#one" = "{count} watching";
|
||||
"lng_video_stream_watched#other" = "{count} watching";
|
||||
"lng_video_stream_edit_stars" = "Edit Stars";
|
||||
"lng_video_stream_remove_stars" = "Remove Stars";
|
||||
"lng_message_stars_ph#one" = "Message for {count} Star";
|
||||
"lng_message_stars_ph#other" = "Message for {count} Stars";
|
||||
"lng_send_text_no" = "Text not allowed.";
|
||||
@@ -4547,6 +4703,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_seen_reacted_none" = "Nobody Reacted";
|
||||
"lng_context_seen_reacted_all" = "Show All Reactions";
|
||||
"lng_context_sent_by" = "Sent by {user}";
|
||||
"lng_context_sent_today" = "Sent today at {time}";
|
||||
"lng_context_sent_yesterday" = "Sent yesterday at {time}";
|
||||
"lng_context_sent_date" = "Sent {date} at {time}";
|
||||
"lng_context_set_as_quick" = "Set As Quick";
|
||||
"lng_context_filter_by_tag" = "Filter by Tag";
|
||||
"lng_context_tag_add_name" = "Add Name";
|
||||
@@ -4633,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!";
|
||||
@@ -4643,6 +4803,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_react_show_in_top" = "Show me in Top Senders";
|
||||
"lng_paid_react_anonymous" = "Anonymous";
|
||||
|
||||
"lng_paid_comment_title" = "Highlight and Pin";
|
||||
"lng_paid_comment_about" = "Highlight and pin your message by adding Stars for {name}.";
|
||||
"lng_paid_comment_button" = "Add {stars}";
|
||||
"lng_paid_comment_pin_about" = "pin in chat";
|
||||
"lng_paid_comment_limit_about#one" = "character";
|
||||
"lng_paid_comment_limit_about#other" = "characters";
|
||||
"lng_paid_comment_emoji_about#one" = "emoji";
|
||||
"lng_paid_comment_emoji_about#other" = "emoji";
|
||||
"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+";
|
||||
"lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
|
||||
@@ -6135,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";
|
||||
@@ -6635,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";
|
||||
@@ -6729,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";
|
||||
@@ -6738,6 +6917,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_stealth_mode_already_about" = "The creators of stories you view in the next **{left}** won't see you in the viewers' lists.";
|
||||
|
||||
"lng_stories_link_invalid" = "This link is broken or has expired.";
|
||||
"lng_stories_live_finished" = "The live story has ended.";
|
||||
|
||||
"lng_stats_title" = "Statistics";
|
||||
"lng_stats_message_title" = "Message Statistic";
|
||||
|
||||
@@ -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.2.6.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,2,6,0
|
||||
PRODUCTVERSION 6,2,6,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.2.6.0"
|
||||
VALUE "FileVersion", "6.3.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.2.6.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,2,6,0
|
||||
PRODUCTVERSION 6,2,6,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.2.6.0"
|
||||
VALUE "FileVersion", "6.3.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.2.6.0"
|
||||
VALUE "ProductVersion", "6.3.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SendOptions {
|
||||
uint64 price = 0;
|
||||
PeerData *sendAs = nullptr;
|
||||
TimeId scheduled = 0;
|
||||
TimeId scheduleRepeatPeriod = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
EffectId effectId = 0;
|
||||
int starsApproved = 0;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -154,6 +154,7 @@ mtpRequestId SuggestMedia(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTPint(), // schedule_date
|
||||
MTPint(), // schedule_repeat_period
|
||||
MTPInputPeer(), // send_as
|
||||
MTPInputQuickReplyShortcut(), // quick_reply_shortcut
|
||||
MTPlong(), // effect
|
||||
@@ -295,6 +296,9 @@ mtpRequestId EditMessage(
|
||||
| (options.scheduled
|
||||
? MTPmessages_EditMessage::Flag::f_schedule_date
|
||||
: emptyFlag)
|
||||
| ((options.scheduled && options.scheduleRepeatPeriod)
|
||||
? MTPmessages_EditMessage::Flag::f_schedule_repeat_period
|
||||
: emptyFlag)
|
||||
| (item->isBusinessShortcut()
|
||||
? MTPmessages_EditMessage::Flag::f_quick_reply_shortcut_id
|
||||
: emptyFlag);
|
||||
@@ -313,6 +317,7 @@ mtpRequestId EditMessage(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
MTP_int(options.scheduleRepeatPeriod),
|
||||
MTP_int(item->shortcutId())
|
||||
)).done([=](
|
||||
const MTPUpdates &result,
|
||||
|
||||
@@ -262,6 +262,9 @@ void GlobalPrivacy::update(
|
||||
: DisallowedFlag())
|
||||
| ((disallowedGiftTypes & DisallowedGiftType::Unique)
|
||||
? DisallowedFlag::f_disallow_unique_stargifts
|
||||
: DisallowedFlag())
|
||||
| ((disallowedGiftTypes & DisallowedGiftType::FromChannels)
|
||||
? DisallowedFlag::f_disallow_stargifts_from_channels
|
||||
: DisallowedFlag());
|
||||
const auto typesWas = _disallowedGiftTypes.current();
|
||||
const auto typesChanged = (typesWas != disallowedGiftTypes);
|
||||
@@ -322,6 +325,9 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
|
||||
| (disallow.is_disallow_premium_gifts()
|
||||
? DisallowedGiftType::Premium
|
||||
: DisallowedGiftType())
|
||||
| (disallow.is_disallow_stargifts_from_channels()
|
||||
? DisallowedGiftType::FromChannels
|
||||
: DisallowedGiftType())
|
||||
| (data.is_display_gifts_button()
|
||||
? DisallowedGiftType::SendHide
|
||||
: DisallowedGiftType());
|
||||
|
||||
@@ -25,11 +25,12 @@ enum class UnarchiveOnNewMessage {
|
||||
};
|
||||
|
||||
enum class DisallowedGiftType : uchar {
|
||||
Limited = 0x01,
|
||||
Unlimited = 0x02,
|
||||
Unique = 0x04,
|
||||
Premium = 0x08,
|
||||
SendHide = 0x10,
|
||||
Limited = 0x01,
|
||||
Unlimited = 0x02,
|
||||
Unique = 0x04,
|
||||
FromChannels = 0x08,
|
||||
Premium = 0x10,
|
||||
SendHide = 0x20,
|
||||
};
|
||||
inline constexpr bool is_flag_type(DisallowedGiftType) { return true; }
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ void Polls::create(
|
||||
}
|
||||
if (action.options.scheduled) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
@@ -95,6 +98,7 @@ void Polls::create(
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
@@ -191,6 +195,7 @@ void Polls::close(not_null<HistoryItem*> item) {
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0), // schedule_date
|
||||
MTP_int(0), // schedule_repeat_period
|
||||
MTPint() // quick_reply_shortcut_id
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace {
|
||||
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.used = data.vused_date().value_or_empty(),
|
||||
.months = data.vmonths().v,
|
||||
.days = data.vdays().v,
|
||||
.giveaway = data.is_via_giveaway(),
|
||||
};
|
||||
}
|
||||
@@ -858,6 +858,8 @@ std::optional<Data::StarGift> FromTL(
|
||||
.releasedBy = releasedBy,
|
||||
.resellTitle = qs(data.vtitle().value_or_empty()),
|
||||
.resellCount = int(data.vavailability_resale().value_or_empty()),
|
||||
.auctionSlug = qs(data.vauction_slug().value_or_empty()),
|
||||
.auctionGiftsPerRound = data.vgifts_per_round().value_or_empty(),
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.perUserTotal = data.vper_user_total().value_or_empty(),
|
||||
|
||||
@@ -30,11 +30,11 @@ struct GiftCode {
|
||||
MsgId giveawayId = 0;
|
||||
TimeId date = 0;
|
||||
TimeId used = 0; // 0 if not used.
|
||||
int months = 0;
|
||||
int days = 0;
|
||||
bool giveaway = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return months != 0;
|
||||
return days != 0;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
|
||||
@@ -101,6 +101,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
@@ -136,6 +139,7 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
MTPReplyMarkup(),
|
||||
MTPvector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
@@ -207,6 +211,9 @@ void SendExistingMedia(
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
@@ -260,6 +267,7 @@ void SendExistingMedia(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
@@ -392,6 +400,9 @@ bool SendDice(MessageToSend &message) {
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
@@ -445,6 +456,7 @@ bool SendDice(MessageToSend &message) {
|
||||
MTPReplyMarkup(),
|
||||
MTP_vector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
|
||||
@@ -66,6 +66,9 @@ void TodoLists::create(
|
||||
}
|
||||
if (action.options.scheduled) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
@@ -100,6 +103,7 @@ void TodoLists::create(
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
|
||||
@@ -23,12 +23,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/gift_auctions.h"
|
||||
#include "data/components/promo_suggestions.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#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"
|
||||
@@ -42,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"
|
||||
@@ -1234,7 +1237,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPFactCheck(),
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong(), // paid_message_stars
|
||||
MTPSuggestedPost()),
|
||||
MTPSuggestedPost(),
|
||||
MTPint()), // schedule_repeat_period
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1274,7 +1278,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPFactCheck(),
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong(), // paid_message_stars
|
||||
MTPSuggestedPost()),
|
||||
MTPSuggestedPost(),
|
||||
MTPint()), // schedule_repeat_period
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1698,13 +1703,21 @@ 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 {
|
||||
owner.histories().checkTopicCreated(id, newId);
|
||||
}
|
||||
session().data().unregisterMessageRandomId(randomId);
|
||||
} else {
|
||||
Core::App().calls().handleUpdate(&session(), update);
|
||||
}
|
||||
session().data().unregisterMessageSentData(randomId);
|
||||
} break;
|
||||
@@ -2141,7 +2154,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
case mtpc_updateGroupCallConnection:
|
||||
case mtpc_updateGroupCall:
|
||||
case mtpc_updateGroupCallMessage:
|
||||
case mtpc_updateGroupCallEncryptedMessage: {
|
||||
case mtpc_updateGroupCallEncryptedMessage:
|
||||
case mtpc_updateDeleteGroupCallMessages: {
|
||||
Core::App().calls().handleUpdate(&session(), update);
|
||||
} break;
|
||||
|
||||
@@ -2784,6 +2798,15 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
|
||||
} break;
|
||||
|
||||
case mtpc_updateStarGiftAuctionState: {
|
||||
const auto &data = update.c_updateStarGiftAuctionState();
|
||||
_session->giftAuctions().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updateStarGiftAuctionUserState: {
|
||||
const auto &data = update.c_updateStarGiftAuctionUserState();
|
||||
_session->giftAuctions().apply(data);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
|
||||
case Key::SavedMusic: return MTP_inputPrivacyKeySavedMusic();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
@@ -244,6 +245,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
case mtpc_privacyKeyNoPaidMessages:
|
||||
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
|
||||
case mtpc_privacyKeySavedMusic:
|
||||
case mtpc_inputPrivacyKeySavedMusic: return Key::SavedMusic;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public:
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
NoPaidMessages,
|
||||
SavedMusic,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
|
||||
@@ -787,8 +787,10 @@ QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
|
||||
const auto peer = story->peer();
|
||||
const auto fallback = [&] {
|
||||
const auto base = peer->username();
|
||||
const auto story = QString::number(storyId.story);
|
||||
const auto query = base + "/s/" + story;
|
||||
const auto id = story->call()
|
||||
? u"live"_q
|
||||
: QString::number(storyId.story);
|
||||
const auto query = base + "/s/" + id;
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
const auto i = _unlikelyStoryLinks.find(storyId);
|
||||
@@ -3473,6 +3475,9 @@ void ApiWrap::forwardMessages(
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= SendFlag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= SendFlag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
@@ -3540,6 +3545,7 @@ void ApiWrap::forwardMessages(
|
||||
? MTP_inputReplyToMonoForum(monoforumPeer->input)
|
||||
: MTPInputReplyTo()),
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTPint(), // video_timestamp
|
||||
@@ -4064,6 +4070,10 @@ void ApiWrap::sendMessage(
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
if (action.options.scheduleRepeatPeriod) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_repeat_period;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_repeat_period;
|
||||
}
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
@@ -4092,6 +4102,7 @@ void ApiWrap::sendMessage(
|
||||
.from = NewMessageFromId(action),
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.scheduleRepeatPeriod = action.options.scheduleRepeatPeriod,
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
@@ -4143,6 +4154,7 @@ void ApiWrap::sendMessage(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId),
|
||||
@@ -4163,6 +4175,7 @@ void ApiWrap::sendMessage(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId),
|
||||
@@ -4470,6 +4483,9 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
: Flag(0))
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| ((options.scheduled && options.scheduleRepeatPeriod)
|
||||
? Flag::f_schedule_repeat_period
|
||||
: Flag(0))
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
@@ -4499,6 +4515,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
MTP_int(options.scheduleRepeatPeriod),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId),
|
||||
@@ -4555,6 +4572,9 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
: Flag(0))
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| (options.scheduleRepeatPeriod
|
||||
? Flag::f_schedule_repeat_period
|
||||
: Flag(0))
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
@@ -4583,6 +4603,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
MTP_int(options.scheduleRepeatPeriod),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId),
|
||||
@@ -4690,6 +4711,9 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
? Flag::f_silent
|
||||
: Flag(0))
|
||||
| (album->options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
//| (album->options.scheduleRepeatPeriod
|
||||
// ? Flag::f_schedule_repeat_period
|
||||
// : Flag(0))
|
||||
| (sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (album->options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
@@ -4710,6 +4734,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_vector<MTPInputSingleMedia>(medias),
|
||||
MTP_int(album->options.scheduled),
|
||||
//MTP_int(album->options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||
MTP_long(album->options.effectId),
|
||||
|
||||
@@ -458,10 +458,12 @@ void AddContactBox::save() {
|
||||
MTP_vector<MTPInputContact>(
|
||||
1,
|
||||
MTP_inputPhoneContact(
|
||||
MTP_flags(0),
|
||||
MTP_long(_contactId),
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName)))
|
||||
MTP_string(lastName),
|
||||
MTPTextWithEntities())) // note
|
||||
)).done(crl::guard(weak, [=](
|
||||
const MTPcontacts_ImportedContacts &result) {
|
||||
const auto &data = result.data();
|
||||
|
||||
@@ -378,10 +378,8 @@ connectionPortInputField: InputField(defaultInputField) {
|
||||
width: 55px;
|
||||
}
|
||||
connectionUserInputField: InputField(defaultInputField) {
|
||||
width: 95px;
|
||||
}
|
||||
connectionPasswordInputField: InputField(defaultInputField) {
|
||||
width: 120px;
|
||||
}
|
||||
connectionIPv6Skip: 11px;
|
||||
|
||||
@@ -929,6 +927,14 @@ scheduleTimeSeparator: FlatLabel(defaultFlatLabel) {
|
||||
}
|
||||
}
|
||||
scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);
|
||||
scheduleRepeatDropdownLock: IconEmoji {
|
||||
icon: icon {{ "emoji/premium_lock", windowActiveTextFg }};
|
||||
padding: margins(-2px, 1px, 0px, 0px);
|
||||
}
|
||||
scheduleRepeatDropdownArrow: IconEmoji {
|
||||
icon: icon {{ "intro_country_dropdown", windowActiveTextFg }};
|
||||
padding: margins(3px, 6px, 3px, 0px);
|
||||
}
|
||||
|
||||
muteBoxTimeField: InputField(scheduleDateField) {
|
||||
textMargins: margins(0px, 0px, 0px, 0px);
|
||||
@@ -998,9 +1004,8 @@ contactsWithStories: PeerList(peerListBox) {
|
||||
statusPosition: point(70px, 27px);
|
||||
|
||||
checkbox: RoundImageCheckbox(defaultPeerListCheckbox) {
|
||||
selectExtendTwice: 1px;
|
||||
imageRadius: 21px;
|
||||
imageSmallRadius: 19px;
|
||||
imageSmallRadius: 18px;
|
||||
check: RoundCheckbox(defaultPeerListCheck) {
|
||||
size: 0px;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
//#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "info/channel_statistics/earn/earn_icons.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
//#include "info/profile/info_profile_badge.h"
|
||||
//#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/boost_box.h" // StartFireworks.
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/controls/table_rows.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
@@ -81,79 +82,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kTooltipDuration = 3 * crl::time(1000);
|
||||
constexpr auto kPriceTooltipDuration = 6 * crl::time(1000);
|
||||
constexpr auto kHorizontalBar = QChar(0x2015);
|
||||
|
||||
struct InfoTooltipData {
|
||||
not_null<Ui::RpWidget*> parent;
|
||||
Ui::ImportantTooltip *raw = nullptr;
|
||||
};
|
||||
|
||||
void ShowInfoTooltip(
|
||||
std::shared_ptr<InfoTooltipData> data,
|
||||
not_null<QWidget*> target,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
int duration) {
|
||||
if (data->raw) {
|
||||
data->raw->toggleAnimated(false);
|
||||
}
|
||||
const auto parent = data->parent;
|
||||
const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
|
||||
parent,
|
||||
Ui::MakeNiceTooltipLabel(
|
||||
parent,
|
||||
std::move(text),
|
||||
st::boxWideWidth,
|
||||
st::defaultImportantTooltipLabel),
|
||||
st::defaultImportantTooltip);
|
||||
tooltip->toggleFast(false);
|
||||
|
||||
base::install_event_filter(tooltip, qApp, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::MouseButtonPress) {
|
||||
tooltip->toggleAnimated(false);
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
const auto update = [=] {
|
||||
const auto geometry = Ui::MapFrom(parent, target, target->rect());
|
||||
const auto countPosition = [=](QSize size) {
|
||||
const auto left = geometry.x()
|
||||
+ (geometry.width() - size.width()) / 2;
|
||||
const auto right = parent->width()
|
||||
- st::normalFont->spacew;
|
||||
return QPoint(
|
||||
std::max(std::min(left, right - size.width()), 0),
|
||||
geometry.y() - size.height() - st::normalFont->descent);
|
||||
};
|
||||
tooltip->pointAt(geometry, RectPart::Top, countPosition);
|
||||
};
|
||||
parent->widthValue(
|
||||
) | rpl::start_with_next(update, tooltip->lifetime());
|
||||
|
||||
update();
|
||||
tooltip->toggleAnimated(true);
|
||||
|
||||
data->raw = tooltip;
|
||||
tooltip->shownValue() | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::start_with_next([=] {
|
||||
crl::on_main(tooltip, [=] {
|
||||
if (tooltip->isHidden()) {
|
||||
if (data->raw == tooltip) {
|
||||
data->raw = nullptr;
|
||||
}
|
||||
delete tooltip;
|
||||
}
|
||||
});
|
||||
}, tooltip->lifetime());
|
||||
|
||||
base::timer_once(
|
||||
duration
|
||||
) | rpl::start_with_next([=] {
|
||||
tooltip->toggleAnimated(false);
|
||||
}, tooltip->lifetime());
|
||||
}
|
||||
using Ui::AddTableRow;
|
||||
using Ui::TableRowTooltipData;
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
@@ -236,8 +168,10 @@ void ShowInfoTooltip(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int months) {
|
||||
return (months < 12)
|
||||
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int days) {
|
||||
return (days < 30)
|
||||
? tr::lng_premium_gift_duration_days
|
||||
: (days < 30 * 12)
|
||||
? tr::lng_premium_gift_duration_months
|
||||
: tr::lng_premium_gift_duration_years;
|
||||
}
|
||||
@@ -260,230 +194,6 @@ void ShowInfoTooltip(
|
||||
: st::giveawayGiftCodeValueMultiline));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeValueWithSmallButton(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
not_null<Ui::RpWidget*> value,
|
||||
rpl::producer<QString> buttonText,
|
||||
Fn<void(not_null<Ui::RpWidget*> button)> handler = nullptr,
|
||||
int topSkip = 0) {
|
||||
class MarginedWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
using RpWidget::RpWidget;
|
||||
QMargins getMargins() const override {
|
||||
return { 0, 0, 0, st::giveawayGiftCodePeerMargin.bottom() };
|
||||
}
|
||||
};
|
||||
auto result = object_ptr<MarginedWidget>(table);
|
||||
const auto raw = result.data();
|
||||
|
||||
value->setParent(raw);
|
||||
value->show();
|
||||
|
||||
const auto button = Ui::CreateChild<Ui::RoundButton>(
|
||||
raw,
|
||||
std::move(buttonText),
|
||||
table->st().smallButton);
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
if (handler) {
|
||||
button->setClickedCallback([button, handler = std::move(handler)] {
|
||||
handler(button);
|
||||
});
|
||||
} else {
|
||||
button->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
button->widthValue(),
|
||||
value->naturalWidthValue()
|
||||
) | rpl::start_with_next([=](int width, int buttonWidth, int) {
|
||||
const auto buttonSkip = st::normalFont->spacew + buttonWidth;
|
||||
value->resizeToNaturalWidth(width - buttonSkip);
|
||||
value->moveToLeft(0, 0, width);
|
||||
button->moveToLeft(
|
||||
rect::right(value) + st::normalFont->spacew,
|
||||
(topSkip
|
||||
+ (table->st().defaultValue.style.font->ascent
|
||||
- table->st().smallButton.style.font->ascent)),
|
||||
width);
|
||||
}, value->lifetime());
|
||||
|
||||
value->heightValue() | rpl::start_with_next([=](int height) {
|
||||
const auto bottom = st::giveawayGiftCodePeerMargin.bottom();
|
||||
raw->resize(raw->width(), height + bottom);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PeerId id,
|
||||
rpl::producer<QString> button = nullptr,
|
||||
Fn<void()> handler = nullptr) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(table);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto &st = st::giveawayGiftCodeUserpic;
|
||||
raw->resize(raw->width(), st.photoSize);
|
||||
|
||||
const auto peer = show->session().data().peer(id);
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
(button && handler) ? peer->shortName() : peer->name(),
|
||||
table->st().defaultValue);
|
||||
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto position = st::giveawayGiftCodeNamePosition;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
}, label->lifetime());
|
||||
|
||||
label->naturalWidthValue() | rpl::start_with_next([=](int width) {
|
||||
raw->setNaturalWidth(st::giveawayGiftCodeNamePosition.x() + width);
|
||||
}, label->lifetime());
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c);
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
show->showBox(PrepareShortInfoBox(peer, show));
|
||||
});
|
||||
|
||||
if (!button || !handler) {
|
||||
return result;
|
||||
}
|
||||
return MakeValueWithSmallButton(
|
||||
table,
|
||||
result.release(),
|
||||
std::move(button),
|
||||
[=](not_null<Ui::RpWidget*> button) { handler(); },
|
||||
st::giveawayGiftCodeNamePosition.y());
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerWithStatusValue(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PeerId id,
|
||||
Fn<void(not_null<Ui::RpWidget*>, EmojiStatusId)> pushStatusId) {
|
||||
auto result = object_ptr<Ui::RpWidget>(table);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto peerLabel = MakePeerTableValue(table, show, id).release();
|
||||
peerLabel->setParent(raw);
|
||||
peerLabel->show();
|
||||
|
||||
raw->resize(raw->width(), peerLabel->height());
|
||||
|
||||
using namespace Info::Profile;
|
||||
struct State {
|
||||
rpl::variable<Badge::Content> content;
|
||||
};
|
||||
const auto peer = show->session().data().peer(id);
|
||||
const auto state = peerLabel->lifetime().make_state<State>();
|
||||
state->content = EmojiStatusIdValue(
|
||||
peer
|
||||
) | rpl::map([=](EmojiStatusId emojiStatusId) {
|
||||
if (!peer->session().premium()
|
||||
|| (!peer->isSelf() && !emojiStatusId)) {
|
||||
return Badge::Content();
|
||||
}
|
||||
return Badge::Content{
|
||||
.badge = BadgeType::Premium,
|
||||
.emojiStatusId = emojiStatusId,
|
||||
};
|
||||
});
|
||||
const auto badge = peerLabel->lifetime().make_state<Badge>(
|
||||
raw,
|
||||
st::infoPeerBadge,
|
||||
&peer->session(),
|
||||
state->content.value(),
|
||||
nullptr,
|
||||
[=] { return show->paused(ChatHelpers::PauseReason::Layer); });
|
||||
state->content.value(
|
||||
) | rpl::start_with_next([=](const Badge::Content &content) {
|
||||
if (const auto widget = badge->widget()) {
|
||||
pushStatusId(widget, content.emojiStatusId);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
rpl::single(rpl::empty) | rpl::then(badge->updated())
|
||||
) | rpl::start_with_next([=](int width, const auto &) {
|
||||
const auto badgeWidget = badge->widget();
|
||||
const auto badgeSkip = badgeWidget
|
||||
? (st::normalFont->spacew + badgeWidget->width())
|
||||
: 0;
|
||||
peerLabel->resizeToNaturalWidth(width - badgeSkip);
|
||||
peerLabel->moveToLeft(0, 0, width);
|
||||
if (badgeWidget) {
|
||||
badgeWidget->moveToLeft(
|
||||
peerLabel->width() + st::normalFont->spacew,
|
||||
st::giftBoxByStarsStarTop,
|
||||
width);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeHiddenPeerTableValue(
|
||||
not_null<Ui::TableLayout*> table) {
|
||||
auto result = object_ptr<Ui::RpWidget>(table);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto &st = st::giveawayGiftCodeUserpic;
|
||||
raw->resize(raw->width(), st.photoSize);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
const auto usize = st.photoSize;
|
||||
userpic->resize(usize, usize);
|
||||
userpic->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(userpic);
|
||||
Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize);
|
||||
}, userpic->lifetime());
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
tr::lng_gift_from_hidden(),
|
||||
table->st().defaultValue);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto position = st::giveawayGiftCodeNamePosition;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setTextColorOverride(st::windowFg->c);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
object_ptr<Ui::RpWidget> value,
|
||||
style::margins valueMargins = st::giveawayGiftCodeValueMargin) {
|
||||
table->addRow(
|
||||
(label
|
||||
? object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(label),
|
||||
table->st().defaultLabel)
|
||||
: object_ptr<Ui::FlatLabel>(nullptr)),
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
valueMargins);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePriceWithChangePercentValue(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
const std::shared_ptr<Data::UniqueGiftValue> &value) {
|
||||
@@ -509,35 +219,13 @@ void AddTableRow(
|
||||
return MakeValueWithSmallButton(table, label, std::move(text));
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePriceValueWithTooltip(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
std::shared_ptr<InfoTooltipData> data,
|
||||
TextWithEntities price,
|
||||
TextWithEntities tooltip) {
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(price),
|
||||
table->st().defaultValue);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto handler = [=](not_null<Ui::RpWidget*> button) {
|
||||
ShowInfoTooltip(
|
||||
data,
|
||||
button,
|
||||
rpl::single(tooltip),
|
||||
kPriceTooltipDuration);
|
||||
};
|
||||
auto text = rpl::single(u"?"_q);
|
||||
return MakeValueWithSmallButton(table, label, std::move(text), handler);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeMinimumPriceValue(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
std::shared_ptr<InfoTooltipData> tooltip,
|
||||
std::shared_ptr<TableRowTooltipData> tooltip,
|
||||
const std::shared_ptr<Data::UniqueGift> &unique) {
|
||||
const auto &value = unique->value;
|
||||
const auto text = FormatValuePrice(value->minimumPrice, value->currency);
|
||||
return MakePriceValueWithTooltip(
|
||||
return Ui::MakeTableValueWithTooltip(
|
||||
table,
|
||||
std::move(tooltip),
|
||||
text,
|
||||
@@ -552,11 +240,11 @@ void AddTableRow(
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeAveragePriceValue(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
std::shared_ptr<InfoTooltipData> tooltip,
|
||||
std::shared_ptr<TableRowTooltipData> tooltip,
|
||||
const std::shared_ptr<Data::UniqueGift> &unique) {
|
||||
const auto &value = unique->value;
|
||||
const auto text = FormatValuePrice(value->averagePrice, value->currency);
|
||||
return MakePriceValueWithTooltip(
|
||||
return Ui::MakeTableValueWithTooltip(
|
||||
table,
|
||||
std::move(tooltip),
|
||||
text,
|
||||
@@ -592,13 +280,12 @@ void AddUniqueGiftPropertyRows(
|
||||
not_null<Ui::RpWidget*> container,
|
||||
not_null<Ui::TableLayout*> table,
|
||||
not_null<Data::UniqueGift*> unique) {
|
||||
const auto tooltip = std::make_shared<InfoTooltipData>(InfoTooltipData{
|
||||
.parent = container,
|
||||
});
|
||||
const auto tooltip = std::make_shared<TableRowTooltipData>(
|
||||
TableRowTooltipData{ .parent = container });
|
||||
const auto showTooltip = [=](
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
rpl::producer<TextWithEntities> text) {
|
||||
ShowInfoTooltip(tooltip, widget, std::move(text), kTooltipDuration);
|
||||
ShowTableRowTooltip(tooltip, widget, std::move(text), kTooltipDuration);
|
||||
};
|
||||
const auto showRarity = [=](
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
@@ -629,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),
|
||||
@@ -712,37 +408,6 @@ void AddUniqueGiftPropertyRows(
|
||||
handler);
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
rpl::producer<TextWithEntities> value,
|
||||
const Ui::Text::MarkedContext &context = {}) {
|
||||
auto widget = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(value),
|
||||
table->st().defaultValue,
|
||||
st::defaultPopupMenu,
|
||||
context);
|
||||
const auto result = widget.data();
|
||||
AddTableRow(table, std::move(label), std::move(widget));
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PeerId id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(label),
|
||||
MakePeerTableValue(table, show, id),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
|
||||
void AddTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
@@ -778,7 +443,7 @@ void AddTable(
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_link_gift_premium(
|
||||
lt_duration,
|
||||
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||
GiftDurationValue(current.days) | Ui::Text::ToWithEntities(),
|
||||
Ui::Text::WithEntities));
|
||||
if (!skipReason && current.from) {
|
||||
const auto reason = AddTableRow(
|
||||
@@ -863,17 +528,22 @@ void ShowAlreadyPremiumToast(
|
||||
|
||||
} // namespace
|
||||
|
||||
rpl::producer<QString> GiftDurationValue(int months) {
|
||||
return GiftDurationPhrase(months)(
|
||||
rpl::producer<QString> GiftDurationValue(int days) {
|
||||
return GiftDurationPhrase(days)(
|
||||
lt_count,
|
||||
rpl::single(float64((months < 12) ? months : (months / 12))));
|
||||
rpl::single(float64((days < 30)
|
||||
? days
|
||||
: (days < 30 * 12)
|
||||
? (days / 30)
|
||||
: (days / (30 * 12)))));
|
||||
}
|
||||
|
||||
QString GiftDuration(int months) {
|
||||
return GiftDurationPhrase(months)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
(months < 12) ? months : (months / 12));
|
||||
QString GiftDuration(int days) {
|
||||
return GiftDurationPhrase(days)(tr::now, lt_count, (days < 30)
|
||||
? days
|
||||
: (days < 30 * 12)
|
||||
? (days / 30)
|
||||
: (days / (30 * 12)));
|
||||
}
|
||||
|
||||
void GiftCodeBox(
|
||||
@@ -1121,9 +791,9 @@ void ResolveGiftCode(
|
||||
code.to = toId;
|
||||
const auto self = (fromId == selfId);
|
||||
const auto peer = session->data().peer(self ? toId : fromId);
|
||||
const auto months = code.months;
|
||||
const auto days = code.days;
|
||||
const auto parent = controller->parentController();
|
||||
Settings::ShowGiftPremium(parent, peer, months, self);
|
||||
Settings::ShowGiftPremium(parent, peer, days, self);
|
||||
} else {
|
||||
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
|
||||
}
|
||||
@@ -1242,7 +912,7 @@ void GiveawayInfoBox(
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
TextWithEntities{ GiftDuration(months * 30) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
@@ -1550,13 +1220,12 @@ void AddStarGiftTable(
|
||||
const auto giftToChannel = entry.giftChannelSavedId
|
||||
&& peerIsChannel(PeerId(entry.bareEntryOwnerId));
|
||||
|
||||
const auto tooltip = std::make_shared<InfoTooltipData>(InfoTooltipData{
|
||||
.parent = container,
|
||||
});
|
||||
const auto tooltip = std::make_shared<TableRowTooltipData>(
|
||||
TableRowTooltipData{ .parent = container });
|
||||
const auto showTooltip = [=](
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
rpl::producer<TextWithEntities> text) {
|
||||
ShowInfoTooltip(tooltip, widget, std::move(text), kTooltipDuration);
|
||||
ShowTableRowTooltip(tooltip, widget, std::move(text), kTooltipDuration);
|
||||
};
|
||||
|
||||
if (unique && entry.bareGiftResaleRecipientId) {
|
||||
@@ -1668,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();
|
||||
@@ -1938,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);
|
||||
@@ -2102,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(
|
||||
@@ -2313,9 +2023,8 @@ void AddUniqueGiftValueTable(
|
||||
MakePriceWithChangePercentValue(table, value));
|
||||
}
|
||||
|
||||
const auto tooltip = std::make_shared<InfoTooltipData>(InfoTooltipData{
|
||||
.parent = container,
|
||||
});
|
||||
const auto tooltip = std::make_shared<TableRowTooltipData>(
|
||||
TableRowTooltipData{ .parent = container });
|
||||
if (value->minimumPrice) {
|
||||
AddTableRow(
|
||||
table,
|
||||
|
||||
@@ -44,8 +44,8 @@ namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
[[nodiscard]] QString GiftDuration(int months);
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int days);
|
||||
[[nodiscard]] QString GiftDuration(int days);
|
||||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
||||
@@ -983,10 +983,11 @@ void PeerListRow::setCheckedInternal(bool checked, anim::type animated) {
|
||||
}
|
||||
|
||||
void PeerListRow::setCustomizedCheckSegments(
|
||||
std::vector<Ui::OutlineSegment> segments) {
|
||||
std::vector<Ui::OutlineSegment> segments,
|
||||
bool liveBadge) {
|
||||
Expects(_checkbox != nullptr);
|
||||
|
||||
_checkbox->setCustomizedSegments(std::move(segments));
|
||||
_checkbox->setCustomizedSegments(std::move(segments), liveBadge);
|
||||
}
|
||||
|
||||
void PeerListRow::finishCheckedAnimation() {
|
||||
|
||||
@@ -223,7 +223,8 @@ public:
|
||||
setCheckedInternal(checked, animated);
|
||||
}
|
||||
void setCustomizedCheckSegments(
|
||||
std::vector<Ui::OutlineSegment> segments);
|
||||
std::vector<Ui::OutlineSegment> segments,
|
||||
bool liveBadge);
|
||||
void setHidden(bool hidden) {
|
||||
_hidden = hidden;
|
||||
}
|
||||
|
||||
@@ -132,27 +132,36 @@ QBrush PeerListStoriesGradient(const style::PeerList &st) {
|
||||
}
|
||||
|
||||
std::vector<Ui::OutlineSegment> PeerListStoriesSegments(
|
||||
int count,
|
||||
int unread,
|
||||
PeerListStoriesCounts counts,
|
||||
const QBrush &unreadBrush) {
|
||||
Expects(unread <= count);
|
||||
Expects(count > 0);
|
||||
Expects(counts.unread <= counts.count);
|
||||
Expects(counts.count > 0);
|
||||
|
||||
auto result = std::vector<Ui::OutlineSegment>();
|
||||
const auto add = [&](bool unread) {
|
||||
result.push_back({
|
||||
.brush = unread ? unreadBrush : st::dialogsUnreadBgMuted->b,
|
||||
.brush = (counts.videoStream
|
||||
? st::attentionButtonFg->b
|
||||
: unread
|
||||
? unreadBrush
|
||||
: st::dialogsUnreadBgMuted->b),
|
||||
.width = (unread
|
||||
? st::dialogsStoriesFull.lineTwice / 2.
|
||||
: st::dialogsStoriesFull.lineReadTwice / 2.),
|
||||
});
|
||||
};
|
||||
result.reserve(count);
|
||||
for (auto i = 0, till = count - unread; i != till; ++i) {
|
||||
add(false);
|
||||
}
|
||||
for (auto i = 0; i != unread; ++i) {
|
||||
if (counts.videoStream) {
|
||||
add(true);
|
||||
} else {
|
||||
const auto count = counts.count;
|
||||
const auto unread = counts.unread;
|
||||
result.reserve(count);
|
||||
for (auto i = 0, till = count - unread; i != till; ++i) {
|
||||
add(false);
|
||||
}
|
||||
for (auto i = 0; i != unread; ++i) {
|
||||
add(true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -521,18 +530,15 @@ void PeerListStories::updateColors() {
|
||||
for (auto i = begin(_counts); i != end(_counts); ++i) {
|
||||
if (const auto row = _delegate->peerListFindRow(i->first)) {
|
||||
if (i->second.count >= 0 && i->second.unread >= 0) {
|
||||
applyForRow(row, i->second.count, i->second.unread, true);
|
||||
applyForRow(row, i->second, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListStories::updateFor(
|
||||
uint64 id,
|
||||
int count,
|
||||
int unread) {
|
||||
void PeerListStories::updateFor(uint64 id, Counts counts) {
|
||||
if (const auto row = _delegate->peerListFindRow(id)) {
|
||||
applyForRow(row, count, unread);
|
||||
applyForRow(row, counts);
|
||||
_delegate->peerListUpdateRow(row);
|
||||
}
|
||||
}
|
||||
@@ -550,11 +556,14 @@ void PeerListStories::process(not_null<PeerListRow*> row) {
|
||||
? 1
|
||||
: 0;
|
||||
const auto unread = source
|
||||
? source->info().unreadCount
|
||||
? int(source->info().unreadCount)
|
||||
: user->hasUnreadStories()
|
||||
? 1
|
||||
: 0;
|
||||
applyForRow(row, count, unread, true);
|
||||
const auto videoStream = source
|
||||
? bool(source->info().hasVideoStream)
|
||||
: user->hasActiveVideoStream();
|
||||
applyForRow(row, { count, unread, videoStream }, true);
|
||||
}
|
||||
|
||||
bool PeerListStories::handleClick(not_null<PeerData*> peer) {
|
||||
@@ -597,25 +606,28 @@ void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
|
||||
const auto info = source
|
||||
? source->info()
|
||||
: Data::StoriesSourceInfo();
|
||||
updateFor(id.value, info.count, info.unreadCount);
|
||||
updateFor(id.value, {
|
||||
int(info.count),
|
||||
int(info.unreadCount),
|
||||
bool(info.hasVideoStream),
|
||||
});
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void PeerListStories::applyForRow(
|
||||
not_null<PeerListRow*> row,
|
||||
int count,
|
||||
int unread,
|
||||
Counts counts,
|
||||
bool force) {
|
||||
auto &counts = _counts[row->id()];
|
||||
if (!force && counts.count == count && counts.unread == unread) {
|
||||
auto &existing = _counts[row->id()];
|
||||
if (!force && existing == counts) {
|
||||
return;
|
||||
}
|
||||
counts.count = count;
|
||||
counts.unread = unread;
|
||||
_delegate->peerListSetRowChecked(row, count > 0);
|
||||
if (count > 0) {
|
||||
existing = counts;
|
||||
_delegate->peerListSetRowChecked(row, counts.count > 0);
|
||||
if (counts.count > 0) {
|
||||
row->setCustomizedCheckSegments(
|
||||
PeerListStoriesSegments(count, unread, _unreadBrush));
|
||||
PeerListStoriesSegments(counts, _unreadBrush),
|
||||
counts.videoStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,18 @@ class SessionController;
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
not_null<Window::SessionController*> sessionController);
|
||||
[[nodiscard]] QBrush PeerListStoriesGradient(const style::PeerList &st);
|
||||
|
||||
struct PeerListStoriesCounts {
|
||||
int count = 0;
|
||||
int unread = 0;
|
||||
bool videoStream = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const PeerListStoriesCounts &a,
|
||||
const PeerListStoriesCounts &b) = default;
|
||||
};
|
||||
[[nodiscard]] std::vector<Ui::OutlineSegment> PeerListStoriesSegments(
|
||||
int count,
|
||||
int unread,
|
||||
PeerListStoriesCounts counts,
|
||||
const QBrush &unreadBrush);
|
||||
|
||||
class PeerListRowWithLink : public PeerListRow {
|
||||
@@ -211,17 +220,13 @@ public:
|
||||
bool handleClick(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
struct Counts {
|
||||
int count = 0;
|
||||
int unread = 0;
|
||||
};
|
||||
using Counts = PeerListStoriesCounts;
|
||||
|
||||
void updateColors();
|
||||
void updateFor(uint64 id, int count, int unread);
|
||||
void updateFor(uint64 id, Counts counts);
|
||||
void applyForRow(
|
||||
not_null<PeerListRow*> row,
|
||||
int count,
|
||||
int unread,
|
||||
Counts counts,
|
||||
bool force = false);
|
||||
|
||||
const not_null<PeerListController*> _controller;
|
||||
|
||||
@@ -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),
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -521,11 +521,6 @@ void Set(
|
||||
} else {
|
||||
peer->changeColorProfileIndex(index);
|
||||
}
|
||||
if (colorCollectible) {
|
||||
peer->changeColorCollectible(*colorCollectible);
|
||||
} else {
|
||||
peer->clearColorCollectible();
|
||||
}
|
||||
peer->changeProfileBackgroundEmojiId(emojiId);
|
||||
} else {
|
||||
if (index == kUnsetColorIndex) {
|
||||
@@ -542,8 +537,10 @@ void Set(
|
||||
}
|
||||
peer->session().changes().peerUpdated(
|
||||
peer,
|
||||
(values.forProfile ? UpdateFlag::ColorProfile : UpdateFlag::Color)
|
||||
| UpdateFlag::BackgroundEmoji);
|
||||
(UpdateFlag::BackgroundEmoji
|
||||
| (values.forProfile
|
||||
? UpdateFlag::ColorProfile
|
||||
: UpdateFlag::Color)));
|
||||
};
|
||||
setLocal(
|
||||
values.colorIndex,
|
||||
@@ -580,10 +577,11 @@ void Set(
|
||||
using ColorFlag = MTPDpeerColor::Flag;
|
||||
send(MTPaccount_UpdateColor(
|
||||
MTP_flags((values.forProfile ? Flag::f_for_profile : Flag(0))
|
||||
| (values.colorIndex != kUnsetColorIndex
|
||||
| (((!values.forProfile && values.colorCollectible)
|
||||
|| (values.colorIndex != kUnsetColorIndex))
|
||||
? Flag::f_color
|
||||
: Flag(0))),
|
||||
(values.colorCollectible
|
||||
((!values.forProfile && values.colorCollectible)
|
||||
? MTP_inputPeerColorCollectible(
|
||||
MTP_long(values.colorCollectible->collectibleId))
|
||||
: MTP_peerColor(
|
||||
@@ -594,6 +592,13 @@ void Set(
|
||||
: ColorFlag(0))),
|
||||
MTP_int(values.colorIndex),
|
||||
MTP_long(values.backgroundEmojiId)))));
|
||||
if (values.statusChanged
|
||||
&& (values.statusId || peer->emojiStatusId())) {
|
||||
peer->owner().emojiStatuses().set(
|
||||
peer,
|
||||
values.statusId,
|
||||
values.statusUntil);
|
||||
}
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
if (peer->isBroadcast()) {
|
||||
using Flag = MTPchannels_UpdateColor::Flag;
|
||||
@@ -648,10 +653,10 @@ void Apply(
|
||||
|
||||
const auto colorMatch = (currentColorIndex == values.colorIndex);
|
||||
const auto emojiMatch = (currentEmojiId == values.backgroundEmojiId);
|
||||
const auto collectibleMatch = (!peer->colorCollectible()
|
||||
== !values.colorCollectible)
|
||||
&& (!peer->colorCollectible()
|
||||
|| (*peer->colorCollectible() == *values.colorCollectible));
|
||||
const auto collectibleMatch = values.forProfile
|
||||
|| ((!peer->colorCollectible() == !values.colorCollectible)
|
||||
&& (!peer->colorCollectible()
|
||||
|| (*peer->colorCollectible() == *values.colorCollectible)));
|
||||
|
||||
if (colorMatch
|
||||
&& emojiMatch
|
||||
@@ -1179,7 +1184,7 @@ void AddGiftSelector(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<uint64> showingGiftIdValue,
|
||||
Fn<void(std::shared_ptr<Data::UniqueGift> selected)> chosen,
|
||||
rpl::producer<std::optional<Ui::ColorCollectible>> selected,
|
||||
rpl::producer<uint64> selected,
|
||||
bool profile,
|
||||
rpl::producer<uint64> selectedGiftId = rpl::single(uint64(0)),
|
||||
Fn<void()> switchToNextTab = nullptr) {
|
||||
@@ -1202,7 +1207,7 @@ void AddGiftSelector(
|
||||
std::vector<bool> validated;
|
||||
std::vector<std::unique_ptr<GiftButton>> buttons;
|
||||
rpl::variable<Ui::VisibleRange> visibleRange;
|
||||
rpl::variable<std::optional<Ui::ColorCollectible>> selected;
|
||||
rpl::variable<uint64> selected;
|
||||
rpl::variable<uint64> selectedGiftId;
|
||||
int perRow = 1;
|
||||
base::unique_qptr<Ui::RpWidget> emptyPlaceholder;
|
||||
@@ -1302,8 +1307,7 @@ void AddGiftSelector(
|
||||
Assert(rowTill >= rowFrom);
|
||||
const auto first = rowFrom * perRow;
|
||||
const auto last = std::min(rowTill * perRow, count);
|
||||
const auto current = state->selected.current();
|
||||
const auto selectedCollectibleId = current ? current->collectibleId : 0;
|
||||
const auto selectedCollectibleId = state->selected.current();
|
||||
const auto selectedGiftId = state->selectedGiftId.current();
|
||||
auto checkedFrom = 0;
|
||||
auto checkedTill = int(buttons.size());
|
||||
@@ -1396,10 +1400,8 @@ void AddGiftSelector(
|
||||
|
||||
state->selected.value(
|
||||
) | rpl::combine_previous() | rpl::start_with_next([=](
|
||||
const std::optional<Ui::ColorCollectible> &was,
|
||||
const std::optional<Ui::ColorCollectible> &now) {
|
||||
const auto wasCollectibleId = was ? was->collectibleId : 0;
|
||||
const auto nowCollectibleId = now ? now->collectibleId : 0;
|
||||
uint64 wasCollectibleId,
|
||||
uint64 nowCollectibleId) {
|
||||
if (wasCollectibleId) {
|
||||
if (const auto button = find(wasCollectibleId)) {
|
||||
button->toggleSelected(false, GiftSelectionMode::Inset);
|
||||
@@ -1616,6 +1618,7 @@ not_null<Info::Profile::TopBar*> CreateProfilePreview(
|
||||
preview->resize(
|
||||
container->width(),
|
||||
st::infoProfileTopBarNoActionsHeightMax);
|
||||
preview->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
return preview;
|
||||
}
|
||||
|
||||
@@ -2062,7 +2065,10 @@ void EditPeerColorSection(
|
||||
? *selected->peerColor
|
||||
: std::optional<Ui::ColorCollectible>();
|
||||
},
|
||||
state->collectible.value(),
|
||||
state->collectible.value() | rpl::map([](
|
||||
const std::optional<Ui::ColorCollectible> &value) {
|
||||
return value ? value->collectibleId : 0;
|
||||
}),
|
||||
false,
|
||||
rpl::single(uint64(0)),
|
||||
switchToNextTab);
|
||||
@@ -2178,6 +2184,8 @@ void EditPeerProfileColorSection(
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme,
|
||||
Fn<void()> aboutCallback) {
|
||||
Expects(peer->isSelf());
|
||||
|
||||
ProcessButton(button);
|
||||
|
||||
const auto preview = CreateProfilePreview(box, container, show, peer);
|
||||
@@ -2188,7 +2196,7 @@ void EditPeerProfileColorSection(
|
||||
struct State {
|
||||
rpl::variable<uint8> index = kUnsetColorIndex;
|
||||
rpl::variable<DocumentId> patternEmojiId;
|
||||
rpl::variable<std::optional<Ui::ColorCollectible>> collectible;
|
||||
rpl::variable<EmojiStatusId> wearable;
|
||||
rpl::variable<uint64> showingGiftId;
|
||||
rpl::variable<uint64> selectedGiftId;
|
||||
std::shared_ptr<Data::UniqueGift> buyCollectible;
|
||||
@@ -2196,23 +2204,25 @@ void EditPeerProfileColorSection(
|
||||
};
|
||||
const auto state = button->lifetime().make_state<State>();
|
||||
state->patternEmojiId = peer->profileBackgroundEmojiId();
|
||||
state->collectible = peer->colorCollectible()
|
||||
? *peer->colorCollectible()
|
||||
: std::optional<Ui::ColorCollectible>();
|
||||
state->wearable = peer->emojiStatusId();
|
||||
|
||||
const auto resetUnique = [=] {
|
||||
preview->setLocalEmojiStatusId({});
|
||||
state->buyCollectible = nullptr;
|
||||
state->collectible.force_assign(std::nullopt);
|
||||
state->wearable = {};
|
||||
};
|
||||
|
||||
const auto setIndex = [=](uint8 index) {
|
||||
state->index = index;
|
||||
if (index != kUnsetColorIndex) {
|
||||
resetUnique();
|
||||
}
|
||||
preview->setColorProfileIndex(index == kUnsetColorIndex
|
||||
? std::nullopt
|
||||
: std::make_optional(index));
|
||||
preview->setPatternEmojiId(state->patternEmojiId.current());
|
||||
resetUnique();
|
||||
preview->setPatternEmojiId(index == kUnsetColorIndex
|
||||
? std::nullopt
|
||||
: std::make_optional(state->patternEmojiId.current()));
|
||||
};
|
||||
setIndex(peer->colorProfileIndex().value_or(kUnsetColorIndex));
|
||||
|
||||
@@ -2320,17 +2330,16 @@ void EditPeerProfileColorSection(
|
||||
&& selected->starsForResale > 0)
|
||||
? selected
|
||||
: nullptr;
|
||||
state->collectible = selected->peerColor
|
||||
? *selected->peerColor
|
||||
: std::optional<Ui::ColorCollectible>();
|
||||
const auto statuses = &peer->owner().emojiStatuses();
|
||||
state->wearable = statuses->fromUniqueGift(*selected);
|
||||
preview->setColorProfileIndex(std::nullopt);
|
||||
preview->setPatternEmojiId(selected->pattern.document->id);
|
||||
preview->setLocalEmojiStatusId(
|
||||
session->data().emojiStatuses().fromUniqueGift(
|
||||
*selected));
|
||||
preview->setLocalEmojiStatusId(state->wearable.current());
|
||||
resetWrap->toggle(true, anim::type::normal);
|
||||
},
|
||||
state->collectible.value(),
|
||||
state->wearable.value() | rpl::map([=](const EmojiStatusId &value) {
|
||||
return value.collectible ? value.collectible->id : 0;
|
||||
}),
|
||||
true,
|
||||
state->selectedGiftId.value(),
|
||||
switchToNextTab);
|
||||
@@ -2347,13 +2356,19 @@ void EditPeerProfileColorSection(
|
||||
} else if (ShowPremiumPreview(show, peer)) {
|
||||
return;
|
||||
}
|
||||
const auto statusId = peer->emojiStatusId();
|
||||
const auto wearable = state->wearable.current();
|
||||
const auto statusChanged = wearable.collectible
|
||||
? (!statusId.collectible
|
||||
|| statusId.collectible->id != wearable.collectible->id)
|
||||
: (statusId.collectible != nullptr);
|
||||
const auto values = SetValues{
|
||||
.colorIndex = state->index.current(),
|
||||
.backgroundEmojiId = state->patternEmojiId.current(),
|
||||
.colorCollectible = state->collectible.current(),
|
||||
.statusId = {},
|
||||
.colorCollectible = std::nullopt,
|
||||
.statusId = state->wearable.current(),
|
||||
.statusUntil = 0,
|
||||
.statusChanged = false,
|
||||
.statusChanged = statusChanged,
|
||||
.forProfile = true,
|
||||
};
|
||||
if (const auto buy = state->buyCollectible) {
|
||||
@@ -2376,15 +2391,17 @@ void EditPeerProfileColorSection(
|
||||
profileState->applying = false;
|
||||
}));
|
||||
});
|
||||
state->collectible.value(
|
||||
) | rpl::start_with_next([=] {
|
||||
state->wearable.value(
|
||||
) | rpl::start_with_next([=](EmojiStatusId id) {
|
||||
const auto buy = state->buyCollectible.get();
|
||||
while (!button->children().isEmpty()) {
|
||||
delete button->children().first();
|
||||
}
|
||||
if (!buy) {
|
||||
button->setText(rpl::combine(
|
||||
tr::lng_settings_color_apply(),
|
||||
(id.collectible
|
||||
? tr::lng_settings_color_wear()
|
||||
: tr::lng_settings_color_apply()),
|
||||
Data::AmPremiumValue(&peer->session())
|
||||
) | rpl::map([=](const QString &text, bool premium) {
|
||||
auto result = TextWithEntities();
|
||||
@@ -2540,6 +2557,12 @@ void SetupPeerColorSample(
|
||||
) | rpl::map([=] {
|
||||
return peer->colorProfileIndex();
|
||||
});
|
||||
auto emojiStatusIdValue = peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::EmojiStatus
|
||||
) | rpl::map([=] {
|
||||
return peer->emojiStatusId();
|
||||
});
|
||||
const auto name = peer->shortName();
|
||||
|
||||
const auto sampleSize = st::settingsColorSampleSize;
|
||||
@@ -2554,36 +2577,77 @@ void SetupPeerColorSample(
|
||||
name);
|
||||
sample->show();
|
||||
|
||||
struct ProfileSampleState {
|
||||
Data::ColorProfileSet colorSet;
|
||||
};
|
||||
const auto profileState
|
||||
= button->lifetime().make_state<ProfileSampleState>();
|
||||
|
||||
const auto profileSample = Ui::CreateChild<Ui::ColorSample>(
|
||||
button.get(),
|
||||
[=, peerColors = &peer->session().api().peerColors()](uint8 index) {
|
||||
return peerColors->colorProfileFor(peer).value_or(
|
||||
Data::ColorProfileSet{});
|
||||
},
|
||||
[=](uint8 index) { return profileState->colorSet; },
|
||||
0,
|
||||
false);
|
||||
profileSample->hide();
|
||||
profileSample->resize(sampleSize, sampleSize);
|
||||
|
||||
const auto emojiStatusWidget = Ui::CreateChild<Ui::RpWidget>(
|
||||
button.get());
|
||||
emojiStatusWidget->hide();
|
||||
emojiStatusWidget->resize(sampleSize, sampleSize);
|
||||
button->lifetime().make_state<std::unique_ptr<Ui::Text::CustomEmoji>>();
|
||||
|
||||
struct EmojiStatusState {
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
};
|
||||
const auto emojiState = button->lifetime().make_state<EmojiStatusState>();
|
||||
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
rpl::duplicate(label),
|
||||
rpl::duplicate(colorIndexValue),
|
||||
rpl::duplicate(colorProfileIndexValue)
|
||||
rpl::duplicate(colorProfileIndexValue),
|
||||
rpl::duplicate(emojiStatusIdValue)
|
||||
) | rpl::start_with_next([=](
|
||||
int width,
|
||||
const QString &buttonText,
|
||||
int colorIndex,
|
||||
std::optional<uint8> profileIndex) {
|
||||
std::optional<uint8> profileIndex,
|
||||
EmojiStatusId emojiStatusId) {
|
||||
const auto available = width
|
||||
- st::settingsButton.padding.left()
|
||||
- (st::settingsColorButton.padding.right() - sampleSize)
|
||||
- st::settingsButton.style.font->width(buttonText)
|
||||
- st::settingsButtonRightSkip;
|
||||
|
||||
const auto hasProfile = profileIndex.has_value();
|
||||
const auto hasEmojiStatus = emojiStatusId
|
||||
&& emojiStatusId.collectible;
|
||||
const auto hasProfile = profileIndex.has_value() || hasEmojiStatus;
|
||||
|
||||
if (hasEmojiStatus && emojiStatusId.collectible) {
|
||||
const auto color = emojiStatusId.collectible->centerColor;
|
||||
profileState->colorSet.palette = { color };
|
||||
profileState->colorSet.bg = { color };
|
||||
profileState->colorSet.story = { color };
|
||||
} else if (hasProfile) {
|
||||
const auto peerColors = &peer->session().api().peerColors();
|
||||
profileState->colorSet
|
||||
= peerColors->colorProfileFor(peer).value_or(
|
||||
Data::ColorProfileSet{});
|
||||
}
|
||||
|
||||
profileSample->setVisible(hasProfile);
|
||||
emojiStatusWidget->setVisible(hasEmojiStatus);
|
||||
|
||||
if (hasEmojiStatus && !emojiState->emoji) {
|
||||
emojiState->emoji
|
||||
= peer->session().data().customEmojiManager().create(
|
||||
Data::EmojiStatusCustomId(emojiStatusId),
|
||||
[raw = emojiStatusWidget] { raw->update(); },
|
||||
Data::CustomEmojiSizeTag::Normal);
|
||||
} else if (!hasEmojiStatus) {
|
||||
emojiState->emoji = nullptr;
|
||||
}
|
||||
|
||||
sample->setForceCircle(hasProfile);
|
||||
if (style->colorPatternIndex(colorIndex) || hasProfile) {
|
||||
@@ -2601,18 +2665,21 @@ void SetupPeerColorSample(
|
||||
? st::settingsColorSampleCutout
|
||||
: 0);
|
||||
profileSample->update();
|
||||
emojiStatusWidget->update();
|
||||
}, sample->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
button->sizeValue(),
|
||||
sample->sizeValue(),
|
||||
rpl::duplicate(colorIndexValue),
|
||||
rpl::duplicate(colorProfileIndexValue)
|
||||
rpl::duplicate(colorProfileIndexValue),
|
||||
rpl::duplicate(emojiStatusIdValue)
|
||||
) | rpl::start_with_next([=](
|
||||
QSize outer,
|
||||
QSize inner,
|
||||
int colorIndex,
|
||||
std::optional<uint8> profileIndex) {
|
||||
std::optional<uint8> profileIndex,
|
||||
EmojiStatusId emojiStatusId) {
|
||||
const auto hasColor = (colorIndex != 0);
|
||||
|
||||
const auto right = st::settingsColorButton.padding.right()
|
||||
@@ -2624,18 +2691,34 @@ void SetupPeerColorSample(
|
||||
sample->move(
|
||||
outer.width() - right - inner.width(),
|
||||
(outer.height() - inner.height()) / 2);
|
||||
profileSample->move(
|
||||
sample->pos().x()
|
||||
+ (hasColor
|
||||
? (st::settingsColorProfileSampleShift
|
||||
- st::settingsColorSampleSize
|
||||
- st::lineWidth)
|
||||
: 0),
|
||||
sample->pos().y());
|
||||
const auto profilePos = sample->pos()
|
||||
+ (hasColor
|
||||
? QPoint(st::settingsColorProfileSampleShift
|
||||
- st::settingsColorSampleSize
|
||||
- st::lineWidth, 0)
|
||||
: QPoint());
|
||||
profileSample->move(profilePos);
|
||||
emojiStatusWidget->move(profilePos);
|
||||
}, sample->lifetime());
|
||||
|
||||
constexpr auto kScale = 0.7;
|
||||
emojiStatusWidget->paintOn([=](QPainter &p) {
|
||||
if (!emojiState->emoji) {
|
||||
return;
|
||||
}
|
||||
const auto size = emojiStatusWidget->size();
|
||||
const auto offset = (size * (1.0 - kScale)) / 2.0;
|
||||
p.translate(offset.width(), offset.height());
|
||||
p.scale(kScale, kScale);
|
||||
emojiState->emoji->paint(p, {
|
||||
.textColor = st::windowFg->c,
|
||||
.now = crl::now(),
|
||||
});
|
||||
});
|
||||
|
||||
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
profileSample->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
emojiStatusWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
void AddPeerColorButton(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -747,7 +747,9 @@ void SendFilesBox::refreshButtons() {
|
||||
_send,
|
||||
_show,
|
||||
_sendMenuDetails,
|
||||
_sendMenuCallback);
|
||||
_sendMenuCallback,
|
||||
&_st.tabbed.menu,
|
||||
&_st.tabbed.icons);
|
||||
}
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
_addFile = addLeftButton(
|
||||
|
||||
@@ -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,
|
||||
@@ -1707,6 +1707,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
const auto commonSendFlags = Flag(0)
|
||||
| Flag::f_with_my_score
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| ((options.scheduled && options.scheduleRepeatPeriod)
|
||||
? Flag::f_schedule_repeat_period
|
||||
: Flag(0))
|
||||
| ((forwardOptions != Data::ForwardOptions::PreserveInfo)
|
||||
? Flag::f_drop_author
|
||||
: Flag(0))
|
||||
@@ -1786,6 +1789,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
? MTP_inputReplyToMonoForum(sublistPeer->input)
|
||||
: MTPInputReplyTo()),
|
||||
MTP_int(options.scheduled),
|
||||
MTP_int(options.scheduleRepeatPeriod),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTP_int(videoTimestamp.value_or(0)),
|
||||
|
||||
1885
Telegram/SourceFiles/boxes/star_gift_auction_box.cpp
Normal file
78
Telegram/SourceFiles/boxes/star_gift_auction_box.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct GiftAuctionState;
|
||||
struct ActiveAuctions;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
struct GiftSendDetails;
|
||||
} // namespace Info::PeerGifts
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class BoxContent;
|
||||
class RoundButton;
|
||||
class GenericBox;
|
||||
|
||||
[[nodiscard]] rpl::lifetime ShowStarGiftAuction(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PeerData *peer,
|
||||
QString slug,
|
||||
Fn<void()> finishRequesting,
|
||||
Fn<void()> boxClosed);
|
||||
|
||||
struct AuctionBidBoxArgs {
|
||||
not_null<PeerData*> peer;
|
||||
std::shared_ptr<ChatHelpers::Show> show;
|
||||
rpl::producer<Data::GiftAuctionState> state;
|
||||
std::unique_ptr<Info::PeerGifts::GiftSendDetails> details;
|
||||
};
|
||||
[[nodiscard]] object_ptr<BoxContent> MakeAuctionBidBox(
|
||||
AuctionBidBoxArgs &&args);
|
||||
|
||||
enum class AuctionButtonCountdownType {
|
||||
Join,
|
||||
Place,
|
||||
};
|
||||
void SetAuctionButtonCountdownText(
|
||||
not_null<RoundButton*> button,
|
||||
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
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace Api {
|
||||
class PremiumGiftCodeOptions;
|
||||
} // namespace Api
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
@@ -18,8 +22,13 @@ struct UniqueGift;
|
||||
struct GiftCode;
|
||||
struct CreditsHistoryEntry;
|
||||
class SavedStarGiftId;
|
||||
struct GiftAuctionState;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
struct GiftDescriptor;
|
||||
} // namespace Info::PeerGifts
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
@@ -44,6 +53,7 @@ class CustomEmoji;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class PopupMenu;
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
@@ -136,18 +146,6 @@ void ShowGiftTransferredToast(
|
||||
not_null<PeerData*> to,
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
void ShowResaleGiftBoughtToast(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<PeerData*> to,
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
[[nodiscard]] rpl::lifetime ShowStarGiftResale(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
uint64 giftId,
|
||||
QString title,
|
||||
Fn<void()> finishRequesting);
|
||||
|
||||
[[nodiscard]] CreditsAmount StarsFromTon(
|
||||
not_null<Main::Session*> session,
|
||||
CreditsAmount ton);
|
||||
@@ -155,4 +153,22 @@ void ShowResaleGiftBoughtToast(
|
||||
not_null<Main::Session*> session,
|
||||
CreditsAmount stars);
|
||||
|
||||
struct GiftsDescriptor {
|
||||
std::vector<Info::PeerGifts::GiftDescriptor> list;
|
||||
std::shared_ptr<Api::PremiumGiftCodeOptions> api;
|
||||
};
|
||||
[[nodiscard]] object_ptr<RpWidget> MakeGiftsSendList(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<GiftsDescriptor> gifts,
|
||||
Fn<void()> loadMore);
|
||||
|
||||
void SendGiftBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
||||
const Info::PeerGifts::GiftDescriptor &descriptor,
|
||||
rpl::producer<Data::GiftAuctionState> auctionState);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
673
Telegram/SourceFiles/boxes/star_gift_resale_box.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/star_gift_resale_box.h"
|
||||
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_common.h"
|
||||
#include "main/main_session.h"
|
||||
#include "menu/gift_resale_filter.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFiltersCount = 4;
|
||||
constexpr auto kResaleBoughtToastDuration = 4 * crl::time(1000);
|
||||
|
||||
using Data::GiftAttributeId;
|
||||
using Data::GiftAttributeIdType;
|
||||
using Data::ResaleGiftsSort;
|
||||
using Data::ResaleGiftsFilter;
|
||||
using Data::ResaleGiftsDescriptor;
|
||||
//using Data::MyGiftsDescriptor;
|
||||
|
||||
[[nodiscard]] Text::String ResaleTabText(QString text) {
|
||||
auto result = Text::String();
|
||||
result.setMarkedText(
|
||||
st::semiboldTextStyle,
|
||||
TextWithEntities{ text }.append(st::giftBoxResaleTabsDropdown),
|
||||
kMarkupTextOptions);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] Text::String SortModeText(ResaleGiftsSort mode) {
|
||||
auto text = [&] {
|
||||
if (mode == ResaleGiftsSort::Number) {
|
||||
return Ui::Text::IconEmoji(&st::giftBoxResaleMiniNumber).append(
|
||||
tr::lng_gift_resale_number(tr::now));
|
||||
} else if (mode == ResaleGiftsSort::Price) {
|
||||
return Ui::Text::IconEmoji(&st::giftBoxResaleMiniPrice).append(
|
||||
tr::lng_gift_resale_price(tr::now));
|
||||
}
|
||||
return Ui::Text::IconEmoji(&st::giftBoxResaleMiniDate).append(
|
||||
tr::lng_gift_resale_date(tr::now));
|
||||
}();
|
||||
auto result = Text::String();
|
||||
result.setMarkedText(
|
||||
st::semiboldTextStyle,
|
||||
text,
|
||||
kMarkupTextOptions);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ResaleTabs {
|
||||
rpl::producer<ResaleGiftsFilter> filter;
|
||||
object_ptr<RpWidget> widget;
|
||||
};
|
||||
[[nodiscard]] ResaleTabs MakeResaleTabs(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const ResaleGiftsDescriptor &info,
|
||||
rpl::producer<ResaleGiftsFilter> filter) {
|
||||
auto widget = object_ptr<RpWidget>((QWidget*)nullptr);
|
||||
const auto raw = widget.data();
|
||||
|
||||
struct Button {
|
||||
QRect geometry;
|
||||
Text::String text;
|
||||
};
|
||||
struct State {
|
||||
rpl::variable<ResaleGiftsFilter> filter;
|
||||
rpl::variable<int> fullWidth;
|
||||
std::vector<Button> buttons;
|
||||
base::unique_qptr<Ui::PopupMenu> menu;
|
||||
ResaleGiftsDescriptor lists;
|
||||
int dragx = 0;
|
||||
int pressx = 0;
|
||||
float64 dragscroll = 0.;
|
||||
float64 scroll = 0.;
|
||||
int scrollMax = 0;
|
||||
int selected = -1;
|
||||
int pressed = -1;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
state->filter = std::move(filter);
|
||||
state->lists.backdrops = info.backdrops;
|
||||
state->lists.models = info.models;
|
||||
state->lists.patterns = info.patterns;
|
||||
|
||||
const auto scroll = [=] {
|
||||
return QPoint(int(base::SafeRound(state->scroll)), 0);
|
||||
};
|
||||
|
||||
static constexpr auto IndexToType = [](int index) {
|
||||
Expects(index > 0 && index < 4);
|
||||
|
||||
return (index == 1)
|
||||
? GiftAttributeIdType::Model
|
||||
: (index == 2)
|
||||
? GiftAttributeIdType::Backdrop
|
||||
: GiftAttributeIdType::Pattern;
|
||||
};
|
||||
|
||||
const auto setSelected = [=](int index) {
|
||||
const auto was = (state->selected >= 0);
|
||||
const auto now = (index >= 0);
|
||||
state->selected = index;
|
||||
if (was != now) {
|
||||
raw->setCursor(now ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
};
|
||||
const auto showMenu = [=](int index) {
|
||||
if (state->menu) {
|
||||
return;
|
||||
}
|
||||
state->menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
raw,
|
||||
st::giftBoxResaleFilter);
|
||||
const auto menu = state->menu.get();
|
||||
const auto modify = [=](Fn<void(ResaleGiftsFilter&)> modifier) {
|
||||
auto now = state->filter.current();
|
||||
modifier(now);
|
||||
state->filter = now;
|
||||
};
|
||||
const auto actionWithIcon = [=](
|
||||
QString text,
|
||||
Fn<void()> callback,
|
||||
not_null<const style::icon*> icon,
|
||||
bool checked = false) {
|
||||
auto action = base::make_unique_q<Ui::GiftResaleFilterAction>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
TextWithEntities{ text },
|
||||
Ui::Text::MarkedContext(),
|
||||
QString(),
|
||||
icon);
|
||||
action->setChecked(checked);
|
||||
action->setClickedCallback(std::move(callback));
|
||||
menu->addAction(std::move(action));
|
||||
};
|
||||
auto context = Core::TextContext({ .session = &show->session() });
|
||||
context.customEmojiFactory = [original = context.customEmojiFactory](
|
||||
QStringView data,
|
||||
const Ui::Text::MarkedContext &context) {
|
||||
return Ui::GiftResaleColorEmoji::Owns(data)
|
||||
? std::make_unique<Ui::GiftResaleColorEmoji>(data)
|
||||
: original(data, context);
|
||||
};
|
||||
const auto actionWithEmoji = [=](
|
||||
TextWithEntities text,
|
||||
Fn<void()> callback,
|
||||
QString data,
|
||||
bool checked) {
|
||||
auto action = base::make_unique_q<Ui::GiftResaleFilterAction>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
std::move(text),
|
||||
context,
|
||||
data,
|
||||
nullptr);
|
||||
action->setChecked(checked);
|
||||
action->setClickedCallback(std::move(callback));
|
||||
menu->addAction(std::move(action));
|
||||
};
|
||||
const auto actionWithDocument = [=](
|
||||
TextWithEntities text,
|
||||
Fn<void()> callback,
|
||||
DocumentId id,
|
||||
bool checked) {
|
||||
actionWithEmoji(
|
||||
std::move(text),
|
||||
std::move(callback),
|
||||
Data::SerializeCustomEmojiId(id),
|
||||
checked);
|
||||
};
|
||||
const auto actionWithColor = [=](
|
||||
TextWithEntities text,
|
||||
Fn<void()> callback,
|
||||
const QColor &color,
|
||||
bool checked) {
|
||||
actionWithEmoji(
|
||||
std::move(text),
|
||||
std::move(callback),
|
||||
Ui::GiftResaleColorEmoji::DataFor(color),
|
||||
checked);
|
||||
};
|
||||
if (!index) {
|
||||
const auto sort = [=](ResaleGiftsSort value) {
|
||||
modify([&](ResaleGiftsFilter &filter) {
|
||||
filter.sort = value;
|
||||
});
|
||||
};
|
||||
const auto is = [&](ResaleGiftsSort value) {
|
||||
return state->filter.current().sort == value;
|
||||
};
|
||||
actionWithIcon(tr::lng_gift_resale_sort_price(tr::now), [=] {
|
||||
sort(ResaleGiftsSort::Price);
|
||||
}, &st::menuIconOrderPrice, is(ResaleGiftsSort::Price));
|
||||
actionWithIcon(tr::lng_gift_resale_sort_date(tr::now), [=] {
|
||||
sort(ResaleGiftsSort::Date);
|
||||
}, &st::menuIconOrderDate, is(ResaleGiftsSort::Date));
|
||||
actionWithIcon(tr::lng_gift_resale_sort_number(tr::now), [=] {
|
||||
sort(ResaleGiftsSort::Number);
|
||||
}, &st::menuIconOrderNumber, is(ResaleGiftsSort::Number));
|
||||
} else {
|
||||
const auto now = state->filter.current().attributes;
|
||||
const auto type = IndexToType(index);
|
||||
const auto has = ranges::contains(
|
||||
now,
|
||||
type,
|
||||
&GiftAttributeId::type);
|
||||
if (has) {
|
||||
actionWithIcon(tr::lng_gift_resale_filter_all(tr::now), [=] {
|
||||
modify([&](ResaleGiftsFilter &filter) {
|
||||
auto &list = filter.attributes;
|
||||
for (auto i = begin(list); i != end(list);) {
|
||||
if (i->type == type) {
|
||||
i = list.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, &st::menuIconSelect);
|
||||
}
|
||||
const auto toggle = [=](GiftAttributeId id) {
|
||||
modify([&](ResaleGiftsFilter &filter) {
|
||||
auto &list = filter.attributes;
|
||||
if (ranges::contains(list, id)) {
|
||||
list.remove(id);
|
||||
} else {
|
||||
list.emplace(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
const auto checked = [=](GiftAttributeId id) {
|
||||
return !has || ranges::contains(now, id);
|
||||
};
|
||||
if (type == GiftAttributeIdType::Model) {
|
||||
for (auto &entry : state->lists.models) {
|
||||
const auto id = IdFor(entry.model);
|
||||
const auto text = TextWithEntities{
|
||||
entry.model.name
|
||||
}.append(' ').append(Ui::Text::Bold(
|
||||
Lang::FormatCountDecimal(entry.count)
|
||||
));
|
||||
actionWithDocument(text, [=] {
|
||||
toggle(id);
|
||||
}, id.value, checked(id));
|
||||
}
|
||||
} else if (type == GiftAttributeIdType::Backdrop) {
|
||||
for (auto &entry : state->lists.backdrops) {
|
||||
const auto id = IdFor(entry.backdrop);
|
||||
const auto text = TextWithEntities{
|
||||
entry.backdrop.name
|
||||
}.append(' ').append(Ui::Text::Bold(
|
||||
Lang::FormatCountDecimal(entry.count)
|
||||
));
|
||||
actionWithColor(text, [=] {
|
||||
toggle(id);
|
||||
}, entry.backdrop.centerColor, checked(id));
|
||||
}
|
||||
} else if (type == GiftAttributeIdType::Pattern) {
|
||||
for (auto &entry : state->lists.patterns) {
|
||||
const auto id = IdFor(entry.pattern);
|
||||
const auto text = TextWithEntities{
|
||||
entry.pattern.name
|
||||
}.append(' ').append(Ui::Text::Bold(
|
||||
Lang::FormatCountDecimal(entry.count)
|
||||
));
|
||||
actionWithDocument(text, [=] {
|
||||
toggle(id);
|
||||
}, id.value, checked(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
menu->popup(QCursor::pos());
|
||||
};
|
||||
|
||||
state->filter.value(
|
||||
) | rpl::start_with_next([=](const ResaleGiftsFilter &fields) {
|
||||
auto x = st::giftBoxResaleTabsMargin.left();
|
||||
auto y = st::giftBoxResaleTabsMargin.top();
|
||||
|
||||
setSelected(-1);
|
||||
state->buttons.resize(kFiltersCount);
|
||||
const auto &list = fields.attributes;
|
||||
const auto setForIndex = [&](int i, auto many, auto one) {
|
||||
const auto type = IndexToType(i);
|
||||
const auto count = ranges::count(
|
||||
list,
|
||||
type,
|
||||
&GiftAttributeId::type);
|
||||
state->buttons[i].text = ResaleTabText((count > 0)
|
||||
? many(tr::now, lt_count, count)
|
||||
: one(tr::now));
|
||||
};
|
||||
state->buttons[0].text = SortModeText(fields.sort);
|
||||
setForIndex(
|
||||
1,
|
||||
tr::lng_gift_resale_models,
|
||||
tr::lng_gift_resale_model);
|
||||
setForIndex(
|
||||
2,
|
||||
tr::lng_gift_resale_backdrops,
|
||||
tr::lng_gift_resale_backdrop);
|
||||
setForIndex(
|
||||
3,
|
||||
tr::lng_gift_resale_symbols,
|
||||
tr::lng_gift_resale_symbol);
|
||||
|
||||
const auto padding = st::giftBoxTabPadding;
|
||||
for (auto &button : state->buttons) {
|
||||
const auto width = button.text.maxWidth();
|
||||
const auto height = st::giftBoxTabStyle.font->height;
|
||||
const auto r = QRect(0, 0, width, height).marginsAdded(padding);
|
||||
button.geometry = QRect(QPoint(x, y), r.size());
|
||||
x += r.width() + st::giftBoxResaleTabSkip;
|
||||
}
|
||||
state->fullWidth = x
|
||||
- st::giftBoxTabSkip
|
||||
+ st::giftBoxTabsMargin.right();
|
||||
const auto height = state->buttons.empty()
|
||||
? 0
|
||||
: (y
|
||||
+ state->buttons.back().geometry.height()
|
||||
+ st::giftBoxTabsMargin.bottom());
|
||||
raw->resize(raw->width(), height);
|
||||
raw->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->fullWidth.value()
|
||||
) | rpl::start_with_next([=](int outer, int inner) {
|
||||
state->scrollMax = std::max(0, inner - outer);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->setMouseTracking(true);
|
||||
raw->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
switch (type) {
|
||||
case QEvent::Leave: setSelected(-1); break;
|
||||
case QEvent::MouseMove: {
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
const auto mousex = me->pos().x();
|
||||
const auto drag = QApplication::startDragDistance();
|
||||
if (state->dragx > 0) {
|
||||
state->scroll = std::clamp(
|
||||
state->dragscroll + state->dragx - mousex,
|
||||
0.,
|
||||
state->scrollMax * 1.);
|
||||
raw->update();
|
||||
break;
|
||||
} else if (state->pressx > 0
|
||||
&& std::abs(state->pressx - mousex) > drag) {
|
||||
state->dragx = state->pressx;
|
||||
state->dragscroll = state->scroll;
|
||||
}
|
||||
const auto position = me->pos() + scroll();
|
||||
for (auto i = 0, c = int(state->buttons.size()); i != c; ++i) {
|
||||
if (state->buttons[i].geometry.contains(position)) {
|
||||
setSelected(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case QEvent::Wheel: {
|
||||
const auto me = static_cast<QWheelEvent*>(e.get());
|
||||
state->scroll = std::clamp(
|
||||
state->scroll - ScrollDeltaF(me).x(),
|
||||
0.,
|
||||
state->scrollMax * 1.);
|
||||
raw->update();
|
||||
} break;
|
||||
case QEvent::MouseButtonPress: {
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
if (me->button() != Qt::LeftButton) {
|
||||
break;
|
||||
}
|
||||
state->pressed = state->selected;
|
||||
state->pressx = me->pos().x();
|
||||
} break;
|
||||
case QEvent::MouseButtonRelease: {
|
||||
const auto me = static_cast<QMouseEvent*>(e.get());
|
||||
if (me->button() != Qt::LeftButton) {
|
||||
break;
|
||||
}
|
||||
const auto dragx = std::exchange(state->dragx, 0);
|
||||
const auto pressed = std::exchange(state->pressed, -1);
|
||||
state->pressx = 0;
|
||||
if (!dragx && pressed >= 0 && state->selected == pressed) {
|
||||
showMenu(pressed);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto padding = st::giftBoxTabPadding;
|
||||
const auto shift = -scroll();
|
||||
for (const auto &button : state->buttons) {
|
||||
const auto geometry = button.geometry.translated(shift);
|
||||
|
||||
p.setBrush(st::giftBoxTabBgActive);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto radius = geometry.height() / 2.;
|
||||
p.drawRoundedRect(geometry, radius, radius);
|
||||
p.setPen(st::giftBoxTabFgActive);
|
||||
|
||||
button.text.draw(p, {
|
||||
.position = geometry.marginsRemoved(padding).topLeft(),
|
||||
.availableWidth = button.text.maxWidth(),
|
||||
});
|
||||
}
|
||||
{
|
||||
const auto &icon = st::defaultEmojiSuggestions;
|
||||
const auto w = icon.fadeRight.width();
|
||||
const auto &c = st::boxDividerBg->c;
|
||||
const auto r = QRect(0, 0, w, raw->height());
|
||||
const auto s = std::abs(float64(shift.x()));
|
||||
constexpr auto kF = 0.5;
|
||||
const auto opacityRight = (state->scrollMax - s)
|
||||
/ (icon.fadeRight.width() * kF);
|
||||
p.setOpacity(std::clamp(std::abs(opacityRight), 0., 1.));
|
||||
icon.fadeRight.fill(p, r.translated(raw->width() - w, 0), c);
|
||||
|
||||
const auto opacityLeft = s / (icon.fadeLeft.width() * kF);
|
||||
p.setOpacity(std::clamp(std::abs(opacityLeft), 0., 1.));
|
||||
icon.fadeLeft.fill(p, r, c);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
return {
|
||||
.filter = state->filter.value(),
|
||||
.widget = std::move(widget),
|
||||
};
|
||||
}
|
||||
|
||||
void GiftResaleBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer,
|
||||
ResaleGiftsDescriptor descriptor) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
// Create a proper vertical layout for the title
|
||||
const auto titleWrap = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box.get()));
|
||||
|
||||
// Add vertical spacing above the title
|
||||
titleWrap->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
titleWrap,
|
||||
st::defaultVerticalListSkip));
|
||||
|
||||
// Add the gift name with semibold style
|
||||
titleWrap->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
titleWrap,
|
||||
rpl::single(descriptor.title),
|
||||
st::boxTitle),
|
||||
QMargins(st::boxRowPadding.left(), 0, st::boxRowPadding.right(), 0));
|
||||
|
||||
// Add the count text in gray below with proper translation
|
||||
const auto countLabel = titleWrap->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
titleWrap,
|
||||
tr::lng_gift_resale_count(tr::now, lt_count, descriptor.count),
|
||||
st::defaultFlatLabel),
|
||||
QMargins(
|
||||
st::boxRowPadding.left(),
|
||||
0,
|
||||
st::boxRowPadding.right(),
|
||||
st::defaultVerticalListSkip));
|
||||
countLabel->setTextColorOverride(st::windowSubTextFg->c);
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
content->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
QPainter(content).fillRect(clip, st::boxDividerBg);
|
||||
}, content->lifetime());
|
||||
|
||||
struct State {
|
||||
rpl::event_stream<> updated;
|
||||
ResaleGiftsDescriptor data;
|
||||
rpl::variable<ResaleGiftsFilter> filter;
|
||||
rpl::variable<bool> ton;
|
||||
rpl::lifetime loading;
|
||||
int lastMinHeight = 0;
|
||||
};
|
||||
const auto state = content->lifetime().make_state<State>();
|
||||
state->data = std::move(descriptor);
|
||||
|
||||
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
||||
|
||||
#ifndef OS_MAC_STORE
|
||||
const auto currency = box->addLeftButton(rpl::single(QString()), [=] {
|
||||
state->ton = !state->ton.current();
|
||||
state->updated.fire({});
|
||||
});
|
||||
currency->setText(rpl::conditional(
|
||||
state->ton.value(),
|
||||
tr::lng_gift_resale_switch_to_stars(),
|
||||
tr::lng_gift_resale_switch_to_ton()));
|
||||
#endif
|
||||
|
||||
box->heightValue() | rpl::start_with_next([=](int height) {
|
||||
if (height > state->lastMinHeight) {
|
||||
state->lastMinHeight = height;
|
||||
box->setMinHeight(height);
|
||||
}
|
||||
}, content->lifetime());
|
||||
|
||||
auto tabs = MakeResaleTabs(
|
||||
window->uiShow(),
|
||||
peer,
|
||||
state->data,
|
||||
state->filter.value());
|
||||
state->filter = std::move(tabs.filter);
|
||||
content->add(std::move(tabs.widget));
|
||||
|
||||
state->filter.changes() | rpl::start_with_next([=](ResaleGiftsFilter value) {
|
||||
state->data.offset = QString();
|
||||
state->loading = ResaleGiftsSlice(
|
||||
&peer->session(),
|
||||
state->data.giftId,
|
||||
value,
|
||||
QString()
|
||||
) | rpl::start_with_next([=](ResaleGiftsDescriptor &&slice) {
|
||||
state->loading.destroy();
|
||||
state->data.offset = slice.list.empty()
|
||||
? QString()
|
||||
: slice.offset;
|
||||
state->data.list = std::move(slice.list);
|
||||
state->updated.fire({});
|
||||
});
|
||||
}, content->lifetime());
|
||||
|
||||
peer->owner().giftUpdates(
|
||||
) | rpl::start_with_next([=](const Data::GiftUpdate &update) {
|
||||
using Action = Data::GiftUpdate::Action;
|
||||
const auto action = update.action;
|
||||
if (action != Action::Transfer && action != Action::ResaleChange) {
|
||||
return;
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
state->data.list,
|
||||
update.slug,
|
||||
[](const Data::StarGift &gift) {
|
||||
return gift.unique ? gift.unique->slug : QString();
|
||||
});
|
||||
if (i == end(state->data.list)) {
|
||||
return;
|
||||
} else if (action == Action::Transfer
|
||||
|| !i->unique->starsForResale) {
|
||||
state->data.list.erase(i);
|
||||
}
|
||||
state->updated.fire({});
|
||||
}, box->lifetime());
|
||||
|
||||
content->add(MakeGiftsSendList(window, peer, rpl::single(
|
||||
rpl::empty
|
||||
) | rpl::then(
|
||||
state->updated.events()
|
||||
) | rpl::map([=] {
|
||||
auto result = GiftsDescriptor();
|
||||
const auto selfId = window->session().userPeerId();
|
||||
const auto forceTon = state->ton.current();
|
||||
for (const auto &gift : state->data.list) {
|
||||
result.list.push_back(Info::PeerGifts::GiftTypeStars{
|
||||
.info = gift,
|
||||
.forceTon = forceTon,
|
||||
.resale = true,
|
||||
.mine = (gift.unique->ownerId == selfId),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}), [=] {
|
||||
if (!state->data.offset.isEmpty()
|
||||
&& !state->loading) {
|
||||
state->loading = ResaleGiftsSlice(
|
||||
&peer->session(),
|
||||
state->data.giftId,
|
||||
state->filter.current(),
|
||||
state->data.offset
|
||||
) | rpl::start_with_next([=](ResaleGiftsDescriptor &&slice) {
|
||||
state->loading.destroy();
|
||||
state->data.offset = slice.list.empty()
|
||||
? QString()
|
||||
: slice.offset;
|
||||
state->data.list.insert(
|
||||
end(state->data.list),
|
||||
std::make_move_iterator(begin(slice.list)),
|
||||
std::make_move_iterator(end(slice.list)));
|
||||
state->updated.fire({});
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowResaleGiftBoughtToast(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<PeerData*> to,
|
||||
const Data::UniqueGift &gift) {
|
||||
show->showToast({
|
||||
.title = tr::lng_gift_sent_title(tr::now),
|
||||
.text = TextWithEntities{ (to->isSelf()
|
||||
? tr::lng_gift_sent_resale_done_self(
|
||||
tr::now,
|
||||
lt_gift,
|
||||
Data::UniqueGiftName(gift))
|
||||
: tr::lng_gift_sent_resale_done(
|
||||
tr::now,
|
||||
lt_user,
|
||||
to->shortName())),
|
||||
},
|
||||
.duration = kResaleBoughtToastDuration,
|
||||
});
|
||||
}
|
||||
|
||||
rpl::lifetime ShowStarGiftResale(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
uint64 giftId,
|
||||
QString title,
|
||||
Fn<void()> finishRequesting) {
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto session = &controller->session();
|
||||
return Data::ResaleGiftsSlice(
|
||||
session,
|
||||
giftId
|
||||
) | rpl::start_with_next([=](ResaleGiftsDescriptor &&info) {
|
||||
if (const auto onstack = finishRequesting) {
|
||||
onstack();
|
||||
}
|
||||
if (!info.giftId || !info.count) {
|
||||
return;
|
||||
}
|
||||
info.title = title;
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(Box(GiftResaleBox, strong, peer, std::move(info)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
41
Telegram/SourceFiles/boxes/star_gift_resale_box.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
struct GiftTypeStars;
|
||||
} // namespace Info::PeerGifts
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ShowResaleGiftBoughtToast(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<PeerData*> to,
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
[[nodiscard]] rpl::lifetime ShowStarGiftResale(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
uint64 giftId,
|
||||
QString title,
|
||||
Fn<void()> finishRequesting);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/star_gift_resale_box.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
@@ -743,22 +743,6 @@ groupCallJoinAsList: PeerList(groupCallInviteMembersList) {
|
||||
statusPosition: point(73px, 28px);
|
||||
}
|
||||
}
|
||||
peerListJoinAsList: PeerList(peerListBox) {
|
||||
item: PeerListItem(peerListBoxItem) {
|
||||
height: 56px;
|
||||
checkbox: RoundImageCheckbox(defaultPeerListCheckbox) {
|
||||
check: RoundCheckbox(defaultRoundCheckbox) {
|
||||
size: 0px;
|
||||
}
|
||||
imageRadius: 19px;
|
||||
imageSmallRadius: 15px;
|
||||
}
|
||||
photoSize: 38px;
|
||||
photoPosition: point(24px, 9px);
|
||||
namePosition: point(73px, 9px);
|
||||
statusPosition: point(73px, 28px);
|
||||
}
|
||||
}
|
||||
groupCallMultiSelect: MultiSelect(defaultMultiSelect) {
|
||||
bg: groupCallMembersBg;
|
||||
item: MultiSelectItem(defaultMultiSelectItem) {
|
||||
@@ -1609,6 +1593,7 @@ confcallLinkButton: RoundButton(defaultActiveButton) {
|
||||
confcallLinkBoxInitial: Box(defaultBox) {
|
||||
buttonPadding: margins(12px, 11px, 24px, 96px);
|
||||
buttonHeight: 42px;
|
||||
buttonWide: true;
|
||||
button: confcallLinkButton;
|
||||
shadowIgnoreTopSkip: true;
|
||||
}
|
||||
@@ -1712,21 +1697,40 @@ groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLi
|
||||
groupCallMessagesScroll: ScrollArea(defaultScrollArea) {
|
||||
barHidden: true;
|
||||
}
|
||||
groupCallMessagePadding: margins(8px, 2px, 8px, 2px);
|
||||
groupCallMessagePadding: margins(8px, 3px, 8px, 2px);
|
||||
groupCallPricePadding: margins(5px, 0px, 5px, 0px);
|
||||
groupCallMessageSkip: 8px;
|
||||
groupCallMessagePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: radialFg;
|
||||
monoFg: radialFg;
|
||||
spoilerFg: radialFg;
|
||||
}
|
||||
groupCallMessageBadge: RoundButton(defaultActiveButton) {
|
||||
textBg: attentionButtonFg;
|
||||
textBgOver: attentionButtonFg;
|
||||
width: -6px;
|
||||
height: 11px;
|
||||
radius: 5px;
|
||||
textTop: 0px;
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(8px semibold);
|
||||
}
|
||||
}
|
||||
groupCallMessageBadgeMargin: margins(0px, 4px, 0px, 0px);
|
||||
groupCallUserpic: 20px;
|
||||
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);
|
||||
|
||||
confcallLinkMenu: IconButton(boxTitleClose) {
|
||||
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
|
||||
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
|
||||
groupCallTopReactorBadge: RoundCheckbox(defaultRoundCheckbox) {
|
||||
width: 1px;
|
||||
border: groupCallMembersBg;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ void Instance::startedConferenceReady(
|
||||
migrationInfo);
|
||||
_currentGroupCall = std::move(_startingGroupCall);
|
||||
_currentGroupCallChanges.fire_copy(call);
|
||||
const auto real = call->conferenceCall().get();
|
||||
const auto real = call->sharedCall().get();
|
||||
const auto link = real->conferenceInviteLink();
|
||||
const auto slug = Group::ExtractConferenceSlug(link);
|
||||
finishConferenceInvitations(args);
|
||||
@@ -600,6 +600,10 @@ void Instance::handleUpdate(
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateGroupCallEncryptedMessage &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateDeleteGroupCallMessages &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [&](const MTPDupdateMessageID &data) {
|
||||
handleGroupCallUpdate(session, update);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Update type in Calls::Instance::handleUpdate.");
|
||||
});
|
||||
@@ -637,6 +641,10 @@ FnMut<void()> Instance::addAsyncWaiter() {
|
||||
};
|
||||
}
|
||||
|
||||
void Instance::registerVideoStream(not_null<GroupCall*> call) {
|
||||
_streams[&call->peer()->session()].push_back(call);
|
||||
}
|
||||
|
||||
bool Instance::isSharingScreen() const {
|
||||
return (_currentCall && _currentCall->isSharingScreen())
|
||||
|| (_currentGroupCall && _currentGroupCall->isSharingScreen());
|
||||
@@ -707,6 +715,35 @@ void Instance::handleCallUpdate(
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update) {
|
||||
if (const auto i = _streams.find(session); i != end(_streams)) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (const auto strong = j->get()) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
strong->handlePossibleCreateOrJoinResponse(data);
|
||||
strong->handleUpdate(update);
|
||||
}, [&](const MTPDupdateGroupCallConnection &data) {
|
||||
strong->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [&](const MTPDupdateGroupCallMessage &data) {
|
||||
strong->handleIncomingMessage(data);
|
||||
}, [&](const MTPDupdateGroupCallEncryptedMessage &data) {
|
||||
strong->handleIncomingMessage(data);
|
||||
}, [&](const MTPDupdateDeleteGroupCallMessages &data) {
|
||||
strong->handleDeleteMessages(data);
|
||||
}, [&](const MTPDupdateMessageID &data) {
|
||||
strong->handleMessageSent(data);
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
strong->handleUpdate(update);
|
||||
}, [&](const MTPDupdateGroupCallChainBlocks &data) {
|
||||
strong->handleUpdate(update);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
++j;
|
||||
} else {
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto groupCall = _currentGroupCall
|
||||
? _currentGroupCall.get()
|
||||
: _startingGroupCall.get();
|
||||
@@ -719,13 +756,19 @@ void Instance::handleGroupCallUpdate(
|
||||
groupCall->handleIncomingMessage(data);
|
||||
}, [&](const MTPDupdateGroupCallEncryptedMessage &data) {
|
||||
groupCall->handleIncomingMessage(data);
|
||||
}, [&](const MTPDupdateDeleteGroupCallMessages &data) {
|
||||
groupCall->handleDeleteMessages(data);
|
||||
}, [&](const MTPDupdateMessageID &data) {
|
||||
groupCall->handleMessageSent(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
if (update.type() == mtpc_updateGroupCallConnection
|
||||
|| update.type() == mtpc_updateGroupCallMessage
|
||||
|| update.type() == mtpc_updateGroupCallEncryptedMessage) {
|
||||
|| update.type() == mtpc_updateGroupCallEncryptedMessage
|
||||
|| update.type() == mtpc_updateDeleteGroupCallMessages
|
||||
|| update.type() == mtpc_updateMessageID) {
|
||||
return;
|
||||
}
|
||||
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
|
||||
@@ -1111,7 +1154,7 @@ void Instance::showConferenceInvite(
|
||||
return;
|
||||
} else if (inGroupCall()
|
||||
&& _currentGroupCall->conference()
|
||||
&& _currentGroupCall->conferenceCall()->id() == conferenceId) {
|
||||
&& _currentGroupCall->sharedCall()->id() == conferenceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,8 @@ public:
|
||||
|
||||
[[nodiscard]] FnMut<void()> addAsyncWaiter();
|
||||
|
||||
void registerVideoStream(not_null<GroupCall*> call);
|
||||
|
||||
[[nodiscard]] bool isSharingScreen() const;
|
||||
[[nodiscard]] bool isQuitPrevent();
|
||||
|
||||
@@ -217,6 +219,10 @@ private:
|
||||
|
||||
base::flat_set<std::unique_ptr<crl::semaphore>> _asyncWaiters;
|
||||
|
||||
base::flat_map<
|
||||
not_null<Main::Session*>,
|
||||
std::vector<base::weak_ptr<GroupCall>>> _streams;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
|
||||
@@ -584,7 +584,7 @@ void TopBar::subscribeToMembersChanges(not_null<GroupCall*> call) {
|
||||
const auto group = _groupCall.get();
|
||||
const auto conference = group && group->conference();
|
||||
auto realValue = conference
|
||||
? (rpl::single(group->conferenceCall().get()) | rpl::type_erased())
|
||||
? (rpl::single(group->sharedCall().get()) | rpl::type_erased())
|
||||
: peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
@@ -224,7 +225,7 @@ void ChooseJoinAsBox(
|
||||
&st::groupCallMultiSelect);
|
||||
} else {
|
||||
controller->setStyleOverrides(
|
||||
&st::peerListJoinAsList,
|
||||
&st::defaultChooseSendAs.list,
|
||||
nullptr);
|
||||
}
|
||||
const auto content = box->addRow(
|
||||
|
||||
@@ -582,7 +582,7 @@ GroupCall::GroupCall(
|
||||
StartConferenceInfo info)
|
||||
: GroupCall(delegate, Group::JoinInfo{
|
||||
.peer = info.call ? info.call->peer() : info.show->session().user(),
|
||||
.joinAs = info.call ? info.call->peer() : info.show->session().user(),
|
||||
.joinAs = info.show ? info.show->session().user() : info.call->peer(),
|
||||
}, info, info.call
|
||||
? info.call->input()
|
||||
: MTP_inputGroupCall(MTP_long(0), MTP_long(0))) {
|
||||
@@ -591,10 +591,10 @@ GroupCall::GroupCall(
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
Group::JoinInfo join,
|
||||
StartConferenceInfo conference,
|
||||
StartConferenceInfo startInfo,
|
||||
const MTPInputGroupCall &inputCall)
|
||||
: _delegate(delegate)
|
||||
, _conferenceCall(std::move(conference.call))
|
||||
, _sharedCall(std::move(startInfo.call))
|
||||
, _peer(join.peer)
|
||||
, _history(_peer->owner().history(_peer))
|
||||
, _api(&_peer->session().mtp())
|
||||
@@ -602,8 +602,8 @@ GroupCall::GroupCall(
|
||||
, _joinAs(join.joinAs)
|
||||
, _possibleJoinAs(std::move(join.possibleJoinAs))
|
||||
, _joinHash(join.joinHash)
|
||||
, _conferenceLinkSlug(conference.linkSlug)
|
||||
, _conferenceJoinMessageId(conference.joinMessageId)
|
||||
, _conferenceLinkSlug(startInfo.linkSlug)
|
||||
, _conferenceJoinMessageId(startInfo.joinMessageId)
|
||||
, _rtmpUrl(join.rtmpInfo.url)
|
||||
, _rtmpKey(join.rtmpInfo.key)
|
||||
, _canManage(Data::CanManageGroupCallValue(_peer))
|
||||
@@ -630,7 +630,7 @@ GroupCall::GroupCall(
|
||||
, _connectingSoundTimer([=] { playConnectingSoundOnce(); })
|
||||
, _listenersHidden(join.rtmp)
|
||||
, _rtmp(join.rtmp)
|
||||
, _rtmpVolume(Group::kDefaultVolume) {
|
||||
, _singleSourceVolume(Group::kDefaultVolume) {
|
||||
applyInputCall(inputCall);
|
||||
|
||||
_muted.value(
|
||||
@@ -664,7 +664,7 @@ GroupCall::GroupCall(
|
||||
if (!canManage() && real->joinMuted()) {
|
||||
_muted = MuteState::ForceMuted;
|
||||
}
|
||||
} else if (!conference.migrating && !conference.show) {
|
||||
} else if (!startInfo.migrating && !startInfo.show) {
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
_peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
@@ -684,21 +684,21 @@ GroupCall::GroupCall(
|
||||
|
||||
setupMediaDevices();
|
||||
setupOutgoingVideo();
|
||||
if (_conferenceCall) {
|
||||
if (_sharedCall && conference()) {
|
||||
setupConferenceCall();
|
||||
initConferenceE2E();
|
||||
} else if (conference.migrating || conference.show) {
|
||||
} else if (!_sharedCall && (startInfo.migrating || startInfo.show)) {
|
||||
initConferenceE2E();
|
||||
}
|
||||
if (conference.migrating || (conference.show && !_conferenceCall)) {
|
||||
if (!conference.muted) {
|
||||
if (startInfo.migrating || (startInfo.show && !_sharedCall)) {
|
||||
if (!startInfo.muted) {
|
||||
setMuted(MuteState::Active);
|
||||
}
|
||||
_startConferenceInfo = std::make_shared<StartConferenceInfo>(
|
||||
std::move(conference));
|
||||
std::move(startInfo));
|
||||
}
|
||||
|
||||
if (_id || (!_conferenceCall && _startConferenceInfo)) {
|
||||
if (_id || (!_sharedCall && _startConferenceInfo)) {
|
||||
initialJoin();
|
||||
} else {
|
||||
start(join.scheduleDate, join.rtmp);
|
||||
@@ -750,6 +750,7 @@ GroupCall::~GroupCall() {
|
||||
if (!_rtmp) {
|
||||
Core::App().mediaDevices().setCaptureMuteTracker(this, false);
|
||||
}
|
||||
_messages->undoScheduledPaidOnDestroy();
|
||||
}
|
||||
|
||||
void GroupCall::initConferenceE2E() {
|
||||
@@ -788,16 +789,16 @@ void GroupCall::initConferenceE2E() {
|
||||
}
|
||||
|
||||
void GroupCall::setupConferenceCall() {
|
||||
Expects(_conferenceCall != nullptr);
|
||||
Expects(_sharedCall != nullptr);
|
||||
|
||||
_conferenceCall->staleParticipantIds(
|
||||
_sharedCall->staleParticipantIds(
|
||||
) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) {
|
||||
removeConferenceParticipants(staleIds, true);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void GroupCall::trackParticipantsWithAccess() {
|
||||
if (!_conferenceCall || !_e2e) {
|
||||
if (!_sharedCall || !_e2e) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -808,7 +809,7 @@ void GroupCall::trackParticipantsWithAccess() {
|
||||
for (const auto &id : set.list) {
|
||||
users.emplace(UserId(id.v));
|
||||
}
|
||||
_conferenceCall->setParticipantsWithAccess(std::move(users));
|
||||
_sharedCall->setParticipantsWithAccess(std::move(users));
|
||||
}, _e2e->lifetime());
|
||||
}
|
||||
|
||||
@@ -959,7 +960,7 @@ void GroupCall::setScheduledDate(TimeId date) {
|
||||
}
|
||||
|
||||
void GroupCall::setMessagesEnabled(bool enabled) {
|
||||
_messagesEnabled = enabled && !_rtmp;
|
||||
_messagesEnabled = enabled && (!_rtmp || videoStream());
|
||||
}
|
||||
|
||||
void GroupCall::subscribeToReal(not_null<Data::GroupCall*> real) {
|
||||
@@ -1198,8 +1199,18 @@ void GroupCall::playConnectingSoundOnce() {
|
||||
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Connecting);
|
||||
}
|
||||
|
||||
not_null<PeerData*> GroupCall::messagesFrom() const {
|
||||
if (!videoStream()) {
|
||||
return joinAs();
|
||||
} else if (const auto real = lookupReal()) {
|
||||
return real->resolveSendAs();
|
||||
}
|
||||
return _peer->session().user();
|
||||
}
|
||||
|
||||
bool GroupCall::showChooseJoinAs() const {
|
||||
return !_rtmp
|
||||
&& !videoStream()
|
||||
&& ((_possibleJoinAs.size() > 1)
|
||||
|| (_possibleJoinAs.size() == 1
|
||||
&& !_possibleJoinAs.front()->isSelf()));
|
||||
@@ -1217,7 +1228,28 @@ bool GroupCall::rtmp() const {
|
||||
}
|
||||
|
||||
bool GroupCall::conference() const {
|
||||
return _conferenceCall || _startConferenceInfo;
|
||||
if (const auto raw = _sharedCall.get()) {
|
||||
return (raw->origin() == Data::GroupCallOrigin::Conference);
|
||||
} else if (_startConferenceInfo.get()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GroupCall::videoStream() const {
|
||||
if (const auto raw = _sharedCall.get()) {
|
||||
return (raw->origin() == Data::GroupCallOrigin::VideoStream);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Data::GroupCallOrigin GroupCall::origin() const {
|
||||
if (const auto raw = _sharedCall.get()) {
|
||||
return raw->origin();
|
||||
} else if (_startConferenceInfo.get()) {
|
||||
return Data::GroupCallOrigin::Conference;
|
||||
}
|
||||
return Data::GroupCallOrigin::Group;
|
||||
}
|
||||
|
||||
bool GroupCall::listenersHidden() const {
|
||||
@@ -1233,7 +1265,7 @@ rpl::producer<bool> GroupCall::emptyRtmpValue() const {
|
||||
}
|
||||
|
||||
int GroupCall::rtmpVolume() const {
|
||||
return _rtmpVolume;
|
||||
return _singleSourceVolume;
|
||||
}
|
||||
|
||||
Calls::Group::RtmpInfo GroupCall::rtmpInfo() const {
|
||||
@@ -1246,15 +1278,15 @@ void GroupCall::setRtmpInfo(const Calls::Group::RtmpInfo &value) {
|
||||
}
|
||||
|
||||
Data::GroupCall *GroupCall::lookupReal() const {
|
||||
if (const auto conference = _conferenceCall.get()) {
|
||||
return conference;
|
||||
if (const auto shared = _sharedCall.get()) {
|
||||
return shared;
|
||||
}
|
||||
const auto real = _peer->groupCall();
|
||||
return (real && real->id() == _id) ? real : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Data::GroupCall> GroupCall::conferenceCall() const {
|
||||
return _conferenceCall;
|
||||
std::shared_ptr<Data::GroupCall> GroupCall::sharedCall() const {
|
||||
return _sharedCall;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
|
||||
@@ -1324,13 +1356,16 @@ void GroupCall::initialJoinRequested() {
|
||||
update.was->ssrc,
|
||||
GetAdditionalAudioSsrc(update.was->videoParams),
|
||||
});
|
||||
} else if (videoStream()) {
|
||||
const auto value = singleSourceVolumeValue();
|
||||
_instance->setVolume(update.now->ssrc, value);
|
||||
} else if (!_rtmp) {
|
||||
updateInstanceVolume(update.was, *update.now);
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
if (_conferenceCall) {
|
||||
_canManage = _conferenceCall->canManage();
|
||||
if (_sharedCall && conference()) {
|
||||
_canManage = _sharedCall->canManage();
|
||||
return;
|
||||
}
|
||||
_peer->session().updates().addActiveChat(
|
||||
@@ -1519,7 +1554,7 @@ void GroupCall::startRejoin() {
|
||||
for (const auto &[task, part] : _broadcastParts) {
|
||||
_api.request(part.requestId).cancel();
|
||||
}
|
||||
if (_conferenceCall || _startConferenceInfo) {
|
||||
if (conference()) {
|
||||
initConferenceE2E();
|
||||
}
|
||||
setState(State::Joining);
|
||||
@@ -1589,10 +1624,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
};
|
||||
LOG(("Call Info: Join payload received, joining with ssrc: %1."
|
||||
).arg(_joinState.payload.ssrc));
|
||||
if (!_conferenceCall && _startConferenceInfo) {
|
||||
if (!_sharedCall && _startConferenceInfo) {
|
||||
startConference();
|
||||
} else if (_conferenceCall
|
||||
&& !_conferenceCall->blockchainMayBeEmpty()
|
||||
} else if (conference()
|
||||
&& _sharedCall
|
||||
&& !_sharedCall->blockchainMayBeEmpty()
|
||||
&& !_e2e->hasLastBlock0()) {
|
||||
refreshLastBlockAndJoin();
|
||||
} else {
|
||||
@@ -1715,13 +1751,13 @@ void GroupCall::startConference() {
|
||||
const MTPUpdates &result,
|
||||
const MTP::Response &response) {
|
||||
_createRequestId = 0;
|
||||
_conferenceCall = _peer->owner().sharedConferenceCallFind(result);
|
||||
if (!_conferenceCall) {
|
||||
_sharedCall = _peer->owner().sharedConferenceCallFind(result);
|
||||
if (!_sharedCall) {
|
||||
joinFail(u"Call not found!"_q);
|
||||
return;
|
||||
}
|
||||
applyInputCall(_conferenceCall->input());
|
||||
_realChanges.fire_copy(_conferenceCall.get());
|
||||
applyInputCall(_sharedCall->input());
|
||||
_realChanges.fire_copy(_sharedCall.get());
|
||||
|
||||
initialJoinRequested();
|
||||
joinDone(
|
||||
@@ -1764,16 +1800,18 @@ void GroupCall::joinDone(
|
||||
_api.request(base::take(state.requestId)).cancel();
|
||||
state.inShortPoll = true;
|
||||
}
|
||||
_messages->setApplyingInitial(true);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
_messages->setApplyingInitial(false);
|
||||
for (auto &state : _subchains) {
|
||||
state.inShortPoll = false;
|
||||
}
|
||||
|
||||
if (justCreated) {
|
||||
subscribeToReal(_conferenceCall.get());
|
||||
subscribeToReal(_sharedCall.get());
|
||||
setupConferenceCall();
|
||||
_conferenceLinkSlug = Group::ExtractConferenceSlug(
|
||||
_conferenceCall->conferenceInviteLink());
|
||||
_sharedCall->conferenceInviteLink());
|
||||
Core::App().calls().startedConferenceReady(
|
||||
this,
|
||||
*_startConferenceInfo);
|
||||
@@ -2068,7 +2106,8 @@ void GroupCall::applyMeInCallLocally() {
|
||||
MTPstring(), // Don't update about text in local updates.
|
||||
MTP_long(raisedHandRating),
|
||||
MTPGroupCallParticipantVideo(),
|
||||
MTPGroupCallParticipantVideo())),
|
||||
MTPGroupCallParticipantVideo(),
|
||||
MTPlong())),
|
||||
MTP_int(0)).c_updateGroupCallParticipants());
|
||||
}
|
||||
|
||||
@@ -2115,7 +2154,8 @@ void GroupCall::applyParticipantLocally(
|
||||
MTPstring(), // Don't update about text in local updates.
|
||||
MTP_long(participant->raisedHandRating),
|
||||
MTPGroupCallParticipantVideo(),
|
||||
MTPGroupCallParticipantVideo())),
|
||||
MTPGroupCallParticipantVideo(),
|
||||
MTPlong())),
|
||||
MTP_int(0)).c_updateGroupCallParticipants());
|
||||
}
|
||||
|
||||
@@ -2383,6 +2423,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
setCameraEndpoint(endpoint ? endpoint->id : std::string());
|
||||
_instance->setJoinResponsePayload(json.toStdString());
|
||||
}
|
||||
updateInstanceVolumes();
|
||||
fillActiveVideoEndpoints();
|
||||
updateRequestedVideoChannels();
|
||||
checkMediaChannelDescriptions();
|
||||
});
|
||||
@@ -2415,6 +2457,26 @@ void GroupCall::handleIncomingMessage(
|
||||
_messages->received(data);
|
||||
}
|
||||
|
||||
void GroupCall::handleDeleteMessages(
|
||||
const MTPDupdateDeleteGroupCallMessages &data) {
|
||||
const auto id = data.vcall().match([&](const MTPDinputGroupCall &data) {
|
||||
return data.vid().v;
|
||||
}, [](const auto &) -> CallId {
|
||||
Unexpected("slug/msg in GroupCall::handleIncomingMessage");
|
||||
});
|
||||
if (id != _id || conference()) {
|
||||
return;
|
||||
}
|
||||
_messages->deleted(data);
|
||||
}
|
||||
|
||||
void GroupCall::handleMessageSent(const MTPDupdateMessageID &data) {
|
||||
if (conference()) {
|
||||
return;
|
||||
}
|
||||
_messages->sent(data);
|
||||
}
|
||||
|
||||
void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v == _id) {
|
||||
LOG(("Call Info: Hangup after groupCallDiscarded."));
|
||||
@@ -3504,6 +3566,10 @@ void GroupCall::updateInstanceMuteState() {
|
||||
&& state != MuteState::PushToTalk);
|
||||
}
|
||||
|
||||
float64 GroupCall::singleSourceVolumeValue() const {
|
||||
return _singleSourceVolume / float64(Group::kDefaultVolume);
|
||||
}
|
||||
|
||||
void GroupCall::updateInstanceVolumes() {
|
||||
const auto real = lookupReal();
|
||||
if (!real) {
|
||||
@@ -3511,8 +3577,12 @@ void GroupCall::updateInstanceVolumes() {
|
||||
}
|
||||
|
||||
if (_rtmp) {
|
||||
const auto value = _rtmpVolume / float64(Group::kDefaultVolume);
|
||||
_instance->setVolume(1, value);
|
||||
_instance->setVolume(1, singleSourceVolumeValue());
|
||||
} else if (videoStream()) {
|
||||
const auto value = singleSourceVolumeValue();
|
||||
for (const auto &participant : real->participants()) {
|
||||
_instance->setVolume(participant.ssrc, value);
|
||||
}
|
||||
} else {
|
||||
const auto &participants = real->participants();
|
||||
for (const auto &participant : participants) {
|
||||
@@ -3905,8 +3975,8 @@ void GroupCall::requestVideoQuality(
|
||||
}
|
||||
|
||||
void GroupCall::toggleMute(const Group::MuteRequest &data) {
|
||||
if (_rtmp) {
|
||||
_rtmpVolume = data.mute ? 0 : Group::kDefaultVolume;
|
||||
if (_rtmp || videoStream()) {
|
||||
_singleSourceVolume = data.mute ? 0 : Group::kDefaultVolume;
|
||||
updateInstanceVolumes();
|
||||
} else if (data.locallyOnly) {
|
||||
applyParticipantLocally(data.peer, data.mute, std::nullopt);
|
||||
@@ -3916,8 +3986,8 @@ void GroupCall::toggleMute(const Group::MuteRequest &data) {
|
||||
}
|
||||
|
||||
void GroupCall::changeVolume(const Group::VolumeRequest &data) {
|
||||
if (_rtmp) {
|
||||
_rtmpVolume = data.volume;
|
||||
if (_rtmp || videoStream()) {
|
||||
_singleSourceVolume = data.volume;
|
||||
updateInstanceVolumes();
|
||||
} else if (data.locallyOnly) {
|
||||
applyParticipantLocally(data.peer, false, data.volume);
|
||||
@@ -3971,7 +4041,7 @@ void GroupCall::inviteToConference(
|
||||
inputCall(),
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
const auto call = _conferenceCall.get();
|
||||
const auto call = _sharedCall.get();
|
||||
user->owner().registerInvitedToCallUser(_id, call, user, true);
|
||||
_peer->session().api().applyUpdates(result);
|
||||
resultAddress()->invited.push_back(user);
|
||||
@@ -4023,7 +4093,7 @@ void GroupCall::inviteUsers(
|
||||
}
|
||||
};
|
||||
|
||||
if (_conferenceCall.get()) {
|
||||
if (_sharedCall.get()) {
|
||||
for (const auto &request : requests) {
|
||||
inviteToConference(request, [=] {
|
||||
return &state->result;
|
||||
@@ -4144,7 +4214,7 @@ std::function<std::vector<uint8_t>(
|
||||
}
|
||||
|
||||
void GroupCall::sendMessage(TextWithTags message) {
|
||||
_messages->send(std::move(message));
|
||||
_messages->send(std::move(message), 0);
|
||||
}
|
||||
|
||||
auto GroupCall::otherParticipantStateValue() const
|
||||
|
||||