Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c4f663941 | ||
|
|
94f9321db9 | ||
|
|
ae70b10cea | ||
|
|
4f685552e7 | ||
|
|
e085a76165 | ||
|
|
30bd3ed013 | ||
|
|
25edab4c94 | ||
|
|
3aa241d825 | ||
|
|
9b867af7fd | ||
|
|
0df3be8630 | ||
|
|
542326af8f | ||
|
|
ea5052e69e | ||
|
|
2dd96b2269 | ||
|
|
627152e2a9 | ||
|
|
f01c93ed58 | ||
|
|
6fe61ed58a | ||
|
|
43347f671c | ||
|
|
0b67fa65f2 | ||
|
|
65b3a36984 | ||
|
|
b08cf75f0b | ||
|
|
0cc21e5ca2 | ||
|
|
48f9a92cc3 | ||
|
|
939882ef68 | ||
|
|
52084cf0ae | ||
|
|
f06f654191 | ||
|
|
356d20542e | ||
|
|
31ea4cfe80 | ||
|
|
1e89ee4e50 | ||
|
|
6fccbf036c | ||
|
|
41d206e354 | ||
|
|
23880ac6c1 | ||
|
|
4439cbf553 | ||
|
|
f506f1b830 | ||
|
|
feb1ea6502 | ||
|
|
fea80b4919 | ||
|
|
373bb8d74c | ||
|
|
3ddefd78ba | ||
|
|
d62e4da163 | ||
|
|
8c60863e11 | ||
|
|
d5be8c8989 | ||
|
|
255b30e88a | ||
|
|
d2dd124be0 | ||
|
|
1053b30a6d | ||
|
|
e7c1073e13 | ||
|
|
0480c6a4af | ||
|
|
c70a49c0f3 | ||
|
|
7840fd481a | ||
|
|
2a8b491c95 | ||
|
|
39c4344047 | ||
|
|
cdb58e4ebd | ||
|
|
972325fb6d | ||
|
|
933b6bedc9 | ||
|
|
2db8a5d00a | ||
|
|
be043ea349 | ||
|
|
e531abf31b | ||
|
|
a6af680e59 | ||
|
|
8292334c9b | ||
|
|
f20c5a1d3c | ||
|
|
d1e2ec0309 | ||
|
|
fe91cae8bc | ||
|
|
7bb30bc4a8 | ||
|
|
2d41d5903b | ||
|
|
df672ffaf5 | ||
|
|
cb100623fb | ||
|
|
30ef7270b3 | ||
|
|
91694a69eb | ||
|
|
cb07bcf0db | ||
|
|
a31e384409 | ||
|
|
4829c6d028 | ||
|
|
ccf6a3fb97 | ||
|
|
2005814fca | ||
|
|
7319665bda | ||
|
|
d74074a21b | ||
|
|
113115f58c | ||
|
|
e84799283d | ||
|
|
71272ed2ec | ||
|
|
6787c338ac | ||
|
|
774a44ac7e | ||
|
|
29cdd358cc | ||
|
|
b9b1bd5e58 | ||
|
|
59814aaeb0 | ||
|
|
fd52b3c23b | ||
|
|
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 |
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
|
||||
|
||||
|
||||
15
.github/workflows/mac.yml
vendored
@@ -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 }}
|
||||
@@ -74,6 +74,15 @@ jobs:
|
||||
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: hugoalh/disk-space-optimizer-ghaction@271735125a1b35180620eae7e45c2e9d470c31b0
|
||||
with:
|
||||
general_include: ".+"
|
||||
homebrew_prune: "True"
|
||||
homebrew_clean: "True"
|
||||
npm_prune: "True"
|
||||
npm_clean: "True"
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v4
|
||||
@@ -95,9 +104,7 @@ jobs:
|
||||
./$REPO_NAME/Telegram/build/prepare/mac.sh skip-release silent
|
||||
|
||||
- name: Free up some disk space.
|
||||
run: |
|
||||
cd Libraries
|
||||
find . -iname "*.dir" -exec rm -rf {} || true \;
|
||||
run: find Libraries -iwholename "*.dir/*" -delete
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
|
||||
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 }}
|
||||
|
||||
15
.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,16 +61,11 @@ jobs:
|
||||
sudo lxd waitready
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: endersonmenezes/free-disk-space@6c4664f43348c8c7011b53488d5ca65e9fc5cd1a
|
||||
uses: samueldr/more-space-action@97048bd0df83fb05b5257887bdbaffc848887673
|
||||
with:
|
||||
remove_android: true
|
||||
remove_dotnet: true
|
||||
remove_haskell: true
|
||||
remove_tool_cache: true
|
||||
remove_swap: true
|
||||
remove_packages: "azure-cli google-cloud-cli microsoft-edge-stable google-chrome-stable firefox postgresql* temurin-* *llvm* mysql* dotnet-sdk-*"
|
||||
remove_packages_one_command: true
|
||||
remove_folders: "/usr/share/swift /usr/share/miniconda /usr/share/az* /usr/share/glade* /usr/local/lib/node_modules /usr/local/share/chromium /usr/local/share/powershell"
|
||||
enable-remove-default-apt-patterns: false
|
||||
enable-lvm-span: true
|
||||
lvm-span-mountpoint: /var/snap/lxd/common/lxd/storage-pools/default/containers
|
||||
|
||||
- name: Telegram Desktop snap build.
|
||||
run: sudo -u $USER snap run snapcraft --verbosity=debug
|
||||
|
||||
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 }}
|
||||
|
||||
@@ -337,6 +337,8 @@ PRIVATE
|
||||
boxes/star_gift_auction_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/star_gift_preview_box.cpp
|
||||
boxes/star_gift_preview_box.h
|
||||
boxes/star_gift_resale_box.cpp
|
||||
boxes/star_gift_resale_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
@@ -523,6 +525,8 @@ PRIVATE
|
||||
data/components/gift_auctions.h
|
||||
data/components/location_pickers.cpp
|
||||
data/components/location_pickers.h
|
||||
data/components/passkeys.cpp
|
||||
data/components/passkeys.h
|
||||
data/components/promo_suggestions.cpp
|
||||
data/components/promo_suggestions.h
|
||||
data/components/recent_peers.cpp
|
||||
@@ -1401,6 +1405,7 @@ PRIVATE
|
||||
platform/linux/specific_linux.h
|
||||
platform/linux/tray_linux.cpp
|
||||
platform/linux/tray_linux.h
|
||||
platform/linux/webauthn_linux.cpp
|
||||
platform/mac/file_utilities_mac.mm
|
||||
platform/mac/file_utilities_mac.h
|
||||
platform/mac/launcher_mac.mm
|
||||
@@ -1420,6 +1425,7 @@ PRIVATE
|
||||
platform/mac/specific_mac_p.h
|
||||
platform/mac/tray_mac.mm
|
||||
platform/mac/tray_mac.h
|
||||
platform/mac/webauthn_mac.mm
|
||||
platform/mac/window_title_mac.mm
|
||||
platform/mac/touchbar/items/mac_formatter_item.h
|
||||
platform/mac/touchbar/items/mac_formatter_item.mm
|
||||
@@ -1454,6 +1460,7 @@ PRIVATE
|
||||
platform/win/specific_win.h
|
||||
platform/win/tray_win.cpp
|
||||
platform/win/tray_win.h
|
||||
platform/win/webauthn_win.cpp
|
||||
platform/win/windows_app_user_model_id.cpp
|
||||
platform/win/windows_app_user_model_id.h
|
||||
platform/win/windows_dlls.cpp
|
||||
@@ -1472,6 +1479,7 @@ PRIVATE
|
||||
platform/platform_overlay_widget.h
|
||||
platform/platform_specific.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_webauthn.h
|
||||
platform/platform_window_title.h
|
||||
profile/profile_back_button.cpp
|
||||
profile/profile_back_button.h
|
||||
@@ -1557,6 +1565,8 @@ PRIVATE
|
||||
settings/settings_notifications.h
|
||||
settings/settings_notifications_type.cpp
|
||||
settings/settings_notifications_type.h
|
||||
settings/settings_passkeys.cpp
|
||||
settings/settings_passkeys.h
|
||||
settings/settings_power_saving.cpp
|
||||
settings/settings_power_saving.h
|
||||
settings/settings_premium.cpp
|
||||
|
||||
BIN
Telegram/Resources/animations/passkeys.tgs
Normal file
|
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 |
@@ -652,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/icons/mediaview/recognize.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
Telegram/Resources/icons/mediaview/recognize@2x.png
Normal file
|
After Width: | Height: | Size: 677 B |
BIN
Telegram/Resources/icons/mediaview/recognize@3x.png
Normal file
|
After Width: | Height: | Size: 1019 B |
BIN
Telegram/Resources/icons/menu/2sv_off.png
Normal file
|
After Width: | Height: | Size: 840 B |
BIN
Telegram/Resources/icons/menu/2sv_off@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/2sv_off@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/menu/2sv_on.png
Normal file
|
After Width: | Height: | Size: 876 B |
BIN
Telegram/Resources/icons/menu/2sv_on@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/2sv_on@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/settings/button_auction.png
Normal file
|
After Width: | Height: | Size: 522 B |
BIN
Telegram/Resources/icons/settings/button_auction@2x.png
Normal file
|
After Width: | Height: | Size: 935 B |
BIN
Telegram/Resources/icons/settings/button_auction@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction.png
Normal file
|
After Width: | Height: | Size: 605 B |
BIN
Telegram/Resources/icons/settings/toast_auction@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/toast_auction@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@@ -378,6 +378,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_intro_qr_step2" = "Go to Settings > Devices > Link Desktop Device";
|
||||
"lng_intro_qr_step3" = "Scan this image to Log In";
|
||||
"lng_intro_qr_skip" = "Or log in using your phone number";
|
||||
"lng_intro_qr_phone" = "Log in using phone number";
|
||||
"lng_intro_qr_passkey" = "Log in using passkey";
|
||||
|
||||
"lng_intro_fragment_title" = "Enter code";
|
||||
"lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment.";
|
||||
@@ -1252,6 +1254,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_restart_now" = "Restart";
|
||||
"lng_settings_restart_later" = "Later";
|
||||
|
||||
"lng_settings_passkeys_title" = "Passkeys";
|
||||
"lng_settings_passkeys_about" = "Manage your passkey, stored safely in the cloud service you choose.";
|
||||
"lng_settings_passkeys_button" = "Add Passkey";
|
||||
"lng_settings_passkeys_button_about" = "Your passkey is stored securely in your password manager. {link}";
|
||||
"lng_settings_passkeys_delete_sure_title" = "Delete Passkey";
|
||||
"lng_settings_passkeys_delete_sure_about" = "Once deleted, this passkey can't be used to log in.";
|
||||
"lng_settings_passkeys_delete_sure_about2" = "Don't forget to remove it from your password manager too.";
|
||||
"lng_settings_passkeys_none_title" = "Protect your account";
|
||||
"lng_settings_passkeys_none_about" = "Log in safely and keep your account secure.";
|
||||
"lng_settings_passkeys_none_info1_title" = "Create a Passkey";
|
||||
"lng_settings_passkeys_none_info1_about" = "Make a passkey to sign in easily and safely.";
|
||||
"lng_settings_passkeys_none_info2_title" = "Log in with face recognition";
|
||||
"lng_settings_passkeys_none_info2_about" = "Use your face, fingerprint, or screen lock to sign in.";
|
||||
"lng_settings_passkeys_none_info3_title" = "Store Passkey securely";
|
||||
"lng_settings_passkeys_none_info3_about" = "Your passkey is stored safely in the cloud service you choose.";
|
||||
"lng_settings_passkeys_none_button" = "Create Passkey";
|
||||
"lng_settings_passkeys_created" = "Created {date}";
|
||||
"lng_settings_passkeys_last_used" = "Last used {date}";
|
||||
"lng_settings_passkeys_unsigned_error" = "Passkeys are not available in unsigned builds. Please use an official signed version of Telegram Desktop.";
|
||||
|
||||
|
||||
"lng_settings_quick_dialog_action_title" = "Chat list quick action";
|
||||
"lng_settings_quick_dialog_action_about" = "Choose the action you want to perform when you middle-click or swipe left in the chat list.";
|
||||
"lng_settings_quick_dialog_action_both" = "Swipe left and Middle-click";
|
||||
@@ -1735,6 +1758,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";
|
||||
@@ -2267,8 +2291,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_proximity_distance_km#other" = "{count} km";
|
||||
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_received_sold" = "{user} sold you a gift for {cost}";
|
||||
"lng_action_gift_unique_received" = "{user} sent you a unique collectible item";
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_sent_sold" = "You sold a gift for {cost}";
|
||||
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
|
||||
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
|
||||
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
|
||||
@@ -2324,6 +2350,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
|
||||
"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
|
||||
"lng_action_gift_got_ton" = "Use TON to suggest posts to channels.";
|
||||
"lng_action_gift_offer" = "{user} offered you {cost} for {name}.";
|
||||
"lng_action_gift_offer_you" = "You offered {cost} for {name}.";
|
||||
"lng_action_gift_offer_state_expires" = "This offer expires in {time}.";
|
||||
"lng_action_gift_offer_time_large" = "{hours} h";
|
||||
"lng_action_gift_offer_time_medium" = "{hours} h {minutes} m";
|
||||
"lng_action_gift_offer_time_small" = "{minutes} m";
|
||||
"lng_action_gift_offer_state_accepted" = "This offer was accepted.";
|
||||
"lng_action_gift_offer_state_rejected" = "This offer was rejected.";
|
||||
"lng_action_gift_offer_state_expired" = "This offer has expired.";
|
||||
"lng_action_gift_offer_sold" = "{user} sold {name} for {cost}.";
|
||||
"lng_action_gift_offer_sold_you" = "You sold {name} for {cost}.";
|
||||
"lng_action_gift_offer_decline" = "Reject";
|
||||
"lng_action_gift_offer_accept" = "Accept";
|
||||
"lng_action_gift_offer_expired" = "The offer from {user} to buy your {name} for {cost} has expired.";
|
||||
"lng_action_gift_offer_expired_your" = "Your offer to buy {name} for {cost} has expired.";
|
||||
"lng_action_gift_offer_declined" = "{user} rejected your offer to buy {name} for {cost}.";
|
||||
"lng_action_gift_offer_declined_you" = "You rejected {user}'s offer to buy your {name} for {cost}.";
|
||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -2807,6 +2850,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_todo_lists" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
|
||||
"lng_premium_summary_subtitle_peer_colors" = "Name and Profile Colors";
|
||||
"lng_premium_summary_about_peer_colors" = "Choose a color and logo for your profile and replies to your messages.";
|
||||
"lng_premium_summary_subtitle_gifts" = "Telegram Gifts";
|
||||
"lng_premium_summary_about_gifts" = "Gifts are collectible items you can trade or showcase on your profile.";
|
||||
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
|
||||
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
@@ -2957,6 +3002,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}";
|
||||
@@ -3094,6 +3140,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
|
||||
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
|
||||
"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}.";
|
||||
"lng_credits_small_balance_for_offer" = "Buy **Stars** to offer for this gift.";
|
||||
"lng_credits_small_balance_for_search" = "Buy **Stars** to search through public posts.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
@@ -3690,6 +3737,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_premium" = "premium";
|
||||
"lng_gift_stars_auction" = "auction";
|
||||
"lng_gift_stars_auction_join" = "Join";
|
||||
"lng_gift_stars_auction_view" = "View";
|
||||
"lng_gift_stars_auction_soon" = "soon";
|
||||
"lng_gift_stars_auction_upgraded" = "upgraded";
|
||||
"lng_gift_stars_your_left#one" = "{count} left";
|
||||
"lng_gift_stars_your_left#other" = "{count} left";
|
||||
"lng_gift_stars_your_finished" = "none left";
|
||||
@@ -3858,6 +3908,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
|
||||
"lng_gift_upgrade_tradable_about_user" = "{name} will be able to sell the gift on Telegram and NFT marketplaces.";
|
||||
"lng_gift_upgrade_tradable_about_channel" = "Admins of {name} will be able to sell the gift on Telegram and NFT marketplaces.";
|
||||
"lng_gift_upgrade_wearable_title" = "Wearable";
|
||||
"lng_gift_upgrade_wearable_about" = "Display gifts on your page and set them as profile covers or statuses.";
|
||||
"lng_gift_upgrade_button" = "Upgrade for {price}";
|
||||
"lng_gift_upgrade_decreases" = "Price decreases in {time}";
|
||||
"lng_gift_upgrade_see_table" = "See how this price will decrease {arrow}";
|
||||
@@ -3910,6 +3962,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_transfer_unlist" = "Unlist";
|
||||
"lng_gift_transfer_locked_title" = "Action Locked";
|
||||
"lng_gift_transfer_locked_text" = "Transfer this gift to your Telegram account on Fragment to unlock this action.";
|
||||
"lng_gift_offer_button" = "Offer to Buy";
|
||||
"lng_gift_offer_title" = "Offer to Buy";
|
||||
"lng_gift_offer_stars_about" = "Choose how many Stars you'd like to offer for {name}.";
|
||||
"lng_gift_offer_ton_about" = "Choose how many TON you'd like to offer for {name}.";
|
||||
"lng_gift_offer_duration" = "Offer Duration";
|
||||
"lng_gift_offer_duration_about" = "Choose how long {user} can accept your offer. When the time expires, the amount will be refunded.";
|
||||
"lng_gift_offer_cost_button" = "Offer {cost}";
|
||||
"lng_gift_offer_reject_title" = "Reject Offer";
|
||||
"lng_gift_offer_confirm_reject" = "Are you sure you want to reject the offer from {user}?";
|
||||
"lng_gift_offer_confirm_accept" = "Do you want to sell {name} to {user} for {cost}?";
|
||||
"lng_gift_offer_you_get" = "You will receive {cost} after fees.";
|
||||
"lng_gift_offer_higher" = "The price you are offered is {percent} higher than the average price for {name}.";
|
||||
"lng_gift_offer_lower" = "The price you are offered is {percent} lower than the average price for {name}.";
|
||||
"lng_gift_offer_sell_for" = "Sell for {price}";
|
||||
"lng_gift_offer_confirm_title" = "Confirm Offer";
|
||||
"lng_gift_offer_confirm_text" = "Do you want to offer {cost} to {user} for {name}?";
|
||||
"lng_gift_offer_table_offer" = "Offer";
|
||||
"lng_gift_offer_table_fee" = "Fee";
|
||||
"lng_gift_offer_table_duration" = "Duration";
|
||||
"lng_gift_sell_unlist_title" = "Unlist {name}";
|
||||
"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?";
|
||||
"lng_gift_sell_title" = "Price in Stars";
|
||||
@@ -3937,6 +4008,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||
"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name.";
|
||||
"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name.";
|
||||
"lng_gift_wear_design_title" = "Unique Profile Design";
|
||||
"lng_gift_wear_design_about" = "Your profile page will get the color and the symbol of this item.";
|
||||
"lng_gift_wear_design_about_channel" = "Your channel page will get the color and the symbol of this item.";
|
||||
"lng_gift_wear_proof_title" = "Proof of Ownership";
|
||||
"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner.";
|
||||
"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner.";
|
||||
@@ -4016,6 +4090,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_text_link" = "Learn more {arrow}";
|
||||
"lng_auction_text_ended" = "Auction ended.";
|
||||
"lng_auction_start_label" = "Started";
|
||||
"lng_auction_starts_label" = "Starts";
|
||||
"lng_auction_rounds_label" = "Rounds";
|
||||
"lng_auction_rounds_exact" = "Round {n}";
|
||||
"lng_auction_rounds_range" = "Rounds {n}-{last}";
|
||||
"lng_auction_rounds_seconds#one" = "{count} minute each";
|
||||
"lng_auction_rounds_seconds#other" = "{count} minutes each";
|
||||
"lng_auction_rounds_minutes#one" = "{count} minute each";
|
||||
"lng_auction_rounds_minutes#other" = "{count} minutes each";
|
||||
"lng_auction_rounds_hours#one" = "{count} hour each";
|
||||
"lng_auction_rounds_hours#other" = "{count} hours each";
|
||||
"lng_auction_rounds_extended" = "{duration} + {increase} for late bids in top {n}";
|
||||
"lng_auction_end_label" = "Ends";
|
||||
"lng_auction_round_label" = "Current Round";
|
||||
"lng_auction_round_value" = "{n} of {amount}";
|
||||
@@ -4030,10 +4115,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_join_starts_in" = "starts in {time}";
|
||||
"lng_auction_join_early_bid" = "Place an Early Bid";
|
||||
"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_title_early" = "Place an Early 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";
|
||||
@@ -4045,6 +4133,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_auction_bid_until" = "until next round";
|
||||
"lng_auction_bid_left#one" = "left";
|
||||
"lng_auction_bid_left#other" = "left";
|
||||
"lng_auction_bid_before_start" = "before start";
|
||||
"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";
|
||||
@@ -4081,13 +4170,34 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_bought_in_round" = "{name} in 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_auction_preview_name" = "Upcoming Auction";
|
||||
"lng_auction_preview_learn_gifts" = "Learn more about Telegram Gifts {arrow}";
|
||||
"lng_auction_preview_variants#one" = "View {emoji} {count} Variant {arrow}";
|
||||
"lng_auction_preview_variants#other" = "View {emoji} {count} Variants {arrow}";
|
||||
"lng_auction_preview_random" = "Random Traits";
|
||||
"lng_auction_preview_selected" = "Selected Traits";
|
||||
"lng_auction_preview_randomize" = "Randomize Traits";
|
||||
"lng_auction_preview_model" = "model";
|
||||
"lng_auction_preview_models#one" = "This collectible features **{count}** unique model.";
|
||||
"lng_auction_preview_models#other" = "This collectible features **{count}** unique models.";
|
||||
"lng_auction_preview_models_button" = "Models";
|
||||
"lng_auction_preview_backdrop" = "backdrop";
|
||||
"lng_auction_preview_backdrops#one" = "This collectible features **{count}** unique backdrop.";
|
||||
"lng_auction_preview_backdrops#other" = "This collectible features **{count}** unique backdrops.";
|
||||
"lng_auction_preview_backdrops_button" = "Backdrops";
|
||||
"lng_auction_preview_symbol" = "symbol";
|
||||
"lng_auction_preview_symbols#one" = "This collectible features **{count}** unique symbol.";
|
||||
"lng_auction_preview_symbols#other" = "This collectible features **{count}** unique symbols.";
|
||||
"lng_auction_preview_symbols_button" = "Symbols";
|
||||
"lng_auction_preview_wear" = "More about wearing gifts {arrow}";
|
||||
"lng_auction_preview_free_upgrade" = "You can upgrade your gift for free, sell it on the market, or set it as your profile cover.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
@@ -4790,6 +4900,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!";
|
||||
@@ -4811,6 +4922,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_reaction_title" = "React with Stars";
|
||||
"lng_paid_reaction_about" = "Highlight and pin your message by sending Stars to {name}.";
|
||||
"lng_paid_reaction_button" = "Send {stars}";
|
||||
"lng_paid_admin_title" = "Receive Stars from Viewers";
|
||||
"lng_paid_admin_about" = "Viewers can send you Star Reactions.";
|
||||
|
||||
"lng_sensitive_tag" = "18+";
|
||||
"lng_sensitive_title" = "18+";
|
||||
@@ -6312,6 +6425,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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";
|
||||
@@ -6806,6 +6920,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";
|
||||
@@ -6900,6 +7015,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";
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<file alias="rtmp.tgs">../../animations/rtmp.tgs</file>
|
||||
<file alias="show_or_premium_lastseen.tgs">../../animations/show_or_premium_lastseen.tgs</file>
|
||||
<file alias="show_or_premium_readtime.tgs">../../animations/show_or_premium_readtime.tgs</file>
|
||||
<file alias="passkeys.tgs">../../animations/passkeys.tgs</file>
|
||||
|
||||
<file alias="profile_muting.tgs">../../animations/profile/profile_muting.tgs</file>
|
||||
<file alias="profile_unmuting.tgs">../../animations/profile/profile_unmuting.tgs</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="6.3.2.0" />
|
||||
Version="6.3.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,2,0
|
||||
PRODUCTVERSION 6,3,2,0
|
||||
FILEVERSION 6,3,6,0
|
||||
PRODUCTVERSION 6,3,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "6.3.2.0"
|
||||
VALUE "FileVersion", "6.3.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.2.0"
|
||||
VALUE "ProductVersion", "6.3.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,3,2,0
|
||||
PRODUCTVERSION 6,3,2,0
|
||||
FILEVERSION 6,3,6,0
|
||||
PRODUCTVERSION 6,3,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "6.3.2.0"
|
||||
VALUE "FileVersion", "6.3.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.3.2.0"
|
||||
VALUE "ProductVersion", "6.3.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -400,7 +400,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
}
|
||||
}
|
||||
const auto replyTo = FullReplyTo();
|
||||
const auto suggest = SuggestPostOptions();
|
||||
const auto suggest = SuggestOptions();
|
||||
Window::PeerMenuCreatePoll(
|
||||
controller,
|
||||
item->history()->peer,
|
||||
|
||||
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Api {
|
||||
|
||||
MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest) {
|
||||
MTPSuggestedPost SuggestToMTP(SuggestOptions suggest) {
|
||||
using Flag = MTPDsuggestedPost::Flag;
|
||||
return suggest.exists
|
||||
? MTP_suggestedPost(
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Api {
|
||||
|
||||
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
|
||||
|
||||
[[nodiscard]] MTPSuggestedPost SuggestToMTP(SuggestPostOptions suggest);
|
||||
[[nodiscard]] MTPSuggestedPost SuggestToMTP(SuggestOptions suggest);
|
||||
|
||||
struct SendOptions {
|
||||
uint64 price = 0;
|
||||
@@ -34,7 +34,7 @@ struct SendOptions {
|
||||
bool invertCaption = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
SuggestPostOptions suggest;
|
||||
SuggestOptions suggest;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendOptions &,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -848,8 +848,22 @@ std::optional<Data::StarGift> FromTL(
|
||||
const auto releasedBy = releasedById
|
||||
? session->data().peer(releasedById).get()
|
||||
: nullptr;
|
||||
const auto background = [&] {
|
||||
if (!data.vbackground()) {
|
||||
return std::shared_ptr<Data::StarGiftBackground>();
|
||||
}
|
||||
const auto &fields = data.vbackground()->data();
|
||||
using namespace Ui;
|
||||
return std::make_shared<Data::StarGiftBackground>(
|
||||
Data::StarGiftBackground{
|
||||
.center = ColorFromSerialized(fields.vcenter_color()),
|
||||
.edge = ColorFromSerialized(fields.vedge_color()),
|
||||
.text = ColorFromSerialized(fields.vtext_color()),
|
||||
});
|
||||
};
|
||||
return std::optional<Data::StarGift>(Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.background = background(),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||
@@ -860,10 +874,12 @@ std::optional<Data::StarGift> FromTL(
|
||||
.resellCount = int(data.vavailability_resale().value_or_empty()),
|
||||
.auctionSlug = qs(data.vauction_slug().value_or_empty()),
|
||||
.auctionGiftsPerRound = data.vgifts_per_round().value_or_empty(),
|
||||
.auctionStartDate = data.vauction_start_date().value_or_empty(),
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.perUserTotal = data.vper_user_total().value_or_empty(),
|
||||
.perUserRemains = data.vper_user_remains().value_or_empty(),
|
||||
.upgradeVariants = data.vupgrade_variants().value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.lockedUntilDate = data.vlocked_until_date().value_or_empty(),
|
||||
@@ -930,6 +946,7 @@ std::optional<Data::StarGift> FromTL(
|
||||
.themeUser = themeUser,
|
||||
.nanoTonForResale = FindTonForResale(data.vresell_amount()),
|
||||
.starsForResale = FindStarsForResale(data.vresell_amount()),
|
||||
.starsMinOffer = data.voffer_min_stars().value_or(-1),
|
||||
.number = data.vnum().v,
|
||||
.onlyAcceptTon = data.is_resale_ton_only(),
|
||||
.canBeTheme = data.is_theme_available(),
|
||||
@@ -942,6 +959,8 @@ std::optional<Data::StarGift> FromTL(
|
||||
data.vvalue_currency().value_or_empty()),
|
||||
.valuePrice = int64(
|
||||
data.vvalue_amount().value_or_empty()),
|
||||
.valuePriceUsd = int64(
|
||||
data.vvalue_usd_amount().value_or_empty()),
|
||||
})
|
||||
: nullptr),
|
||||
.peerColor = colorCollectible,
|
||||
@@ -963,7 +982,7 @@ std::optional<Data::StarGift> FromTL(
|
||||
unique->originalDetails = FromTL(session, data);
|
||||
});
|
||||
}
|
||||
return std::make_optional(result);
|
||||
return std::make_optional(std::move(result));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1009,6 +1028,7 @@ std::optional<Data::SavedStarGift> FromTL(
|
||||
? peerFromMTP(*data.vfrom_id())
|
||||
: PeerId()),
|
||||
.date = data.vdate().v,
|
||||
.giftNum = data.vgift_num().value_or_empty(),
|
||||
.upgradeSeparate = data.is_upgrade_separate(),
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/transfer_gift_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/components/credits.h"
|
||||
@@ -44,7 +45,7 @@ void SendApproval(
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId scheduleDate = 0) {
|
||||
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion
|
||||
|| suggestion->accepted
|
||||
|| suggestion->rejected
|
||||
@@ -56,7 +57,7 @@ void SendApproval(
|
||||
const auto session = &show->session();
|
||||
const auto finish = [=] {
|
||||
if (const auto item = session->data().message(id)) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (suggestion) {
|
||||
suggestion->requestId = 0;
|
||||
}
|
||||
@@ -83,7 +84,7 @@ void ConfirmApproval(
|
||||
not_null<HistoryItem*> item,
|
||||
TimeId scheduleDate = 0,
|
||||
Fn<void()> accepted = nullptr) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion
|
||||
|| suggestion->accepted
|
||||
|| suggestion->rejected
|
||||
@@ -244,7 +245,7 @@ void SendDecline(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &comment) {
|
||||
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion
|
||||
|| suggestion->accepted
|
||||
|| suggestion->rejected
|
||||
@@ -256,7 +257,7 @@ void SendDecline(
|
||||
const auto session = &show->session();
|
||||
const auto finish = [=] {
|
||||
if (const auto item = session->data().message(id)) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (suggestion) {
|
||||
suggestion->requestId = 0;
|
||||
}
|
||||
@@ -365,10 +366,10 @@ void SendSuggest(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item,
|
||||
std::shared_ptr<SendSuggestState> state,
|
||||
Fn<void(SuggestPostOptions&)> modify,
|
||||
Fn<void(SuggestOptions&)> modify,
|
||||
Fn<void()> done = nullptr,
|
||||
int starsApproved = 0) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
const auto id = item->fullId();
|
||||
const auto withPaymentApproved = [=](int stars) {
|
||||
if (const auto item = show->session().data().message(id)) {
|
||||
@@ -416,7 +417,7 @@ void SendSuggest(
|
||||
void SuggestApprovalDate(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion) {
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +438,7 @@ void SuggestApprovalDate(
|
||||
show,
|
||||
item,
|
||||
state,
|
||||
[=](SuggestPostOptions &options) { options.date = result; },
|
||||
[=](SuggestOptions &options) { options.date = result; },
|
||||
close);
|
||||
};
|
||||
using namespace HistoryView;
|
||||
@@ -454,12 +455,12 @@ void SuggestApprovalDate(
|
||||
void SuggestOfferForMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item,
|
||||
SuggestPostOptions values,
|
||||
SuggestOptions values,
|
||||
HistoryView::SuggestMode mode) {
|
||||
const auto id = item->fullId();
|
||||
const auto state = std::make_shared<SendSuggestState>();
|
||||
const auto weak = std::make_shared<base::weak_qptr<Ui::BoxContent>>();
|
||||
const auto done = [=](SuggestPostOptions result) {
|
||||
const auto done = [=](SuggestOptions result) {
|
||||
const auto item = show->session().data().message(id);
|
||||
if (!item) {
|
||||
return;
|
||||
@@ -473,7 +474,7 @@ void SuggestOfferForMessage(
|
||||
show,
|
||||
item,
|
||||
state,
|
||||
[=](SuggestPostOptions &options) { options = result; },
|
||||
[=](SuggestOptions &options) { options = result; },
|
||||
close);
|
||||
};
|
||||
using namespace HistoryView;
|
||||
@@ -490,7 +491,7 @@ void SuggestOfferForMessage(
|
||||
void SuggestApprovalPrice(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion) {
|
||||
return;
|
||||
}
|
||||
@@ -504,6 +505,20 @@ void SuggestApprovalPrice(
|
||||
}, SuggestMode::Change);
|
||||
}
|
||||
|
||||
void ConfirmGiftSaleAccept(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion) {
|
||||
ShowGiftSaleAcceptBox(window, item, suggestion);
|
||||
}
|
||||
|
||||
void ConfirmGiftSaleDecline(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion) {
|
||||
ShowGiftSaleRejectBox(window, item, suggestion);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<ClickHandler> AcceptClickHandler(
|
||||
@@ -521,9 +536,11 @@ std::shared_ptr<ClickHandler> AcceptClickHandler(
|
||||
return;
|
||||
}
|
||||
const auto show = controller->uiShow();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion) {
|
||||
return;
|
||||
} else if (suggestion->gift) {
|
||||
ConfirmGiftSaleAccept(controller, item, suggestion);
|
||||
} else if (!suggestion->date) {
|
||||
RequestApprovalDate(show, item);
|
||||
} else {
|
||||
@@ -546,7 +563,12 @@ std::shared_ptr<ClickHandler> DeclineClickHandler(
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
RequestDeclineComment(controller->uiShow(), item);
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (suggestion && suggestion->gift) {
|
||||
ConfirmGiftSaleDecline(controller, item, suggestion);
|
||||
} else {
|
||||
RequestDeclineComment(controller->uiShow(), item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -573,7 +595,7 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
|
||||
const auto suggestion = item->Get<HistoryMessageSuggestion>();
|
||||
if (!suggestion) {
|
||||
return;
|
||||
}
|
||||
@@ -594,7 +616,7 @@ std::shared_ptr<ClickHandler> SuggestChangesClickHandler(
|
||||
.messageId = FullMsgId(history->peer->id, item->id),
|
||||
.monoforumPeerId = monoforumPeerId,
|
||||
},
|
||||
SuggestPostOptions{
|
||||
SuggestOptions{
|
||||
.exists = uint32(1),
|
||||
.priceWhole = uint32(suggestion->price.whole()),
|
||||
.priceNano = uint32(suggestion->price.nano()),
|
||||
|
||||
@@ -24,7 +24,7 @@ void ToggleExistingMedia(
|
||||
Data::FileOrigin origin,
|
||||
ToggleRequestCallback toggleRequest,
|
||||
DoneCallback &&done) {
|
||||
const auto api = &document->owner().session().api();
|
||||
const auto api = &document->session().api();
|
||||
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
const auto usedFileReference = document->fileReference();
|
||||
|
||||
@@ -3483,6 +3483,9 @@ void ApiWrap::forwardMessages(
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= SendFlag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= SendFlag::f_effect;
|
||||
}
|
||||
if (draft.options != Data::ForwardOptions::PreserveInfo) {
|
||||
sendFlags |= SendFlag::f_drop_author;
|
||||
}
|
||||
@@ -3548,6 +3551,7 @@ void ApiWrap::forwardMessages(
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_long(starsPaid),
|
||||
Api::SuggestToMTP(action.options.suggest)
|
||||
|
||||
@@ -232,7 +232,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
@@ -273,7 +273,7 @@ void EditCaptionBox::StartMediaReplace(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
@@ -304,7 +304,7 @@ void EditCaptionBox::StartMediaReplace(
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
@@ -353,7 +353,7 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
@@ -117,7 +117,7 @@ private:
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<HistoryItem*> _historyItem;
|
||||
const SuggestPostOptions _suggest;
|
||||
const SuggestOptions _suggest;
|
||||
const bool _isAllowedEditMedia;
|
||||
const Ui::AlbumType _albumType;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1616,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);
|
||||
@@ -1780,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(
|
||||
|
||||
@@ -97,6 +97,7 @@ using RightsMap = std::vector<std::pair<ChatAdminRight, tr::phrase<>>>;
|
||||
{ Flag::ManageCall, tr::lng_request_channel_manage_livestreams },
|
||||
{ Flag::ManageDirect, tr::lng_request_channel_manage_direct },
|
||||
{ Flag::AddAdmins, tr::lng_request_channel_add_admins },
|
||||
{ Flag::BanUsers, tr::lng_request_group_ban_users },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -246,7 +246,8 @@ ChatAdminRightsInfo EditAdminBox::defaultRights() const {
|
||||
| Flag::DeleteStories
|
||||
| Flag::InviteByLinkOrAdd
|
||||
| Flag::ManageCall
|
||||
| Flag::ManageDirect) };
|
||||
| Flag::ManageDirect
|
||||
| Flag::BanUsers) };
|
||||
}
|
||||
|
||||
void EditAdminBox::prepare() {
|
||||
|
||||
@@ -117,13 +117,7 @@ base::unique_qptr<Ui::RpWidget> CreateEmptyPlaceholder(
|
||||
tr::lng_gift_stars_tabs_my_empty_next(
|
||||
lt_emoji,
|
||||
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
TextWithEntities::Simple
|
||||
) | rpl::map([](TextWithEntities t) {
|
||||
return Ui::Text::Wrapped(
|
||||
std::move(t),
|
||||
EntityType::Url,
|
||||
u"internal:"_q);
|
||||
}),
|
||||
tr::link),
|
||||
st::giftBoxGiftEmptyLabel)
|
||||
: nullptr;
|
||||
if (emptyNextLabel) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ constexpr auto kDefaultChargeStars = 10;
|
||||
{ Flag::ManageCall, tr::lng_rights_channel_manage_calls(tr::now) },
|
||||
{ Flag::ManageDirect, tr::lng_rights_channel_manage_direct(tr::now) },
|
||||
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
|
||||
{ Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
|
||||
};
|
||||
return {
|
||||
{ std::nullopt, std::move(first) },
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_domain.h" // kMaxAccounts
|
||||
@@ -43,7 +44,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_credits.h" // upgradeGiftSubtext
|
||||
#include "styles/style_info.h" // infoStarsUnderstood
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
@@ -137,6 +141,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_todo_lists();
|
||||
case PremiumFeature::PeerColors:
|
||||
return tr::lng_premium_summary_subtitle_peer_colors();
|
||||
case PremiumFeature::Gifts:
|
||||
return tr::lng_premium_summary_subtitle_gifts();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_subtitle_location();
|
||||
@@ -206,6 +212,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_todo_lists();
|
||||
case PremiumFeature::PeerColors:
|
||||
return tr::lng_premium_summary_about_peer_colors();
|
||||
case PremiumFeature::Gifts:
|
||||
return tr::lng_premium_summary_about_gifts();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_about_location();
|
||||
@@ -548,6 +556,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::Effects: return "effects";
|
||||
case PremiumFeature::TodoLists: return "todo";
|
||||
case PremiumFeature::PeerColors: return "peer_colors";
|
||||
case PremiumFeature::Gifts: return "gifts";
|
||||
|
||||
case PremiumFeature::BusinessLocation: return "business_location";
|
||||
case PremiumFeature::BusinessHours: return "business_hours";
|
||||
@@ -894,6 +903,45 @@ struct VideoPreviewDocument {
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddGiftsInfoRows(not_null<Ui::VerticalLayout*> container) {
|
||||
const auto infoRow = [&](
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> text,
|
||||
not_null<const style::icon*> icon) {
|
||||
auto raw = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container));
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title) | Ui::Text::ToBold(),
|
||||
st::defaultFlatLabel),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st::upgradeGiftSubtext),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
*icon,
|
||||
st::starrefInfoIconPosition);
|
||||
};
|
||||
|
||||
infoRow(
|
||||
tr::lng_gift_upgrade_unique_title(),
|
||||
tr::lng_gift_upgrade_unique_about(),
|
||||
&st::menuIconUnique);
|
||||
infoRow(
|
||||
tr::lng_gift_upgrade_tradable_title(),
|
||||
tr::lng_gift_upgrade_tradable_about(),
|
||||
&st::menuIconTradable);
|
||||
infoRow(
|
||||
tr::lng_gift_upgrade_wearable_title(),
|
||||
tr::lng_gift_upgrade_wearable_about(),
|
||||
&st::menuIconNftWear);
|
||||
}
|
||||
|
||||
void PreviewBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
@@ -956,22 +1004,32 @@ void PreviewBox(
|
||||
st::settingsPremiumTopBarClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
const auto left = Ui::CreateChild<Ui::IconButton>(
|
||||
const auto gifts = (state->selected.current() == PremiumFeature::Gifts);
|
||||
|
||||
const auto left = gifts ? nullptr : Ui::CreateChild<Ui::IconButton>(
|
||||
buttonsParent,
|
||||
st::settingsPremiumMoveLeft);
|
||||
left->setClickedCallback([=] { move(-1); });
|
||||
if (left) {
|
||||
left->setClickedCallback([=] { move(-1); });
|
||||
}
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::IconButton>(
|
||||
const auto right = gifts ? nullptr : Ui::CreateChild<Ui::IconButton>(
|
||||
buttonsParent,
|
||||
st::settingsPremiumMoveRight);
|
||||
right->setClickedCallback([=] { move(1); });
|
||||
if (right) {
|
||||
right->setClickedCallback([=] { move(1); });
|
||||
}
|
||||
|
||||
buttonsParent->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto outerHeight = st::premiumPreviewHeight;
|
||||
close->moveToRight(0, 0, width);
|
||||
left->moveToLeft(0, (outerHeight - left->height()) / 2, width);
|
||||
right->moveToRight(0, (outerHeight - right->height()) / 2, width);
|
||||
if (left) {
|
||||
left->moveToLeft(0, (outerHeight - left->height()) / 2, width);
|
||||
}
|
||||
if (right) {
|
||||
right->moveToRight(0, (outerHeight - right->height()) / 2, width);
|
||||
}
|
||||
}, close->lifetime());
|
||||
|
||||
state->preload = [=] {
|
||||
@@ -1099,16 +1157,30 @@ void PreviewBox(
|
||||
st::premiumPreviewAboutPadding,
|
||||
style::al_top
|
||||
)->setTryMakeSimilarLines(true);
|
||||
box->addRow(
|
||||
CreateSwitch(box->verticalLayout(), &state->selected, state->order),
|
||||
st::premiumDotsMargin);
|
||||
|
||||
if (gifts) {
|
||||
box->setStyle(st::giftBox);
|
||||
AddGiftsInfoRows(box->verticalLayout());
|
||||
} else {
|
||||
box->addRow(
|
||||
CreateSwitch(box->verticalLayout(), &state->selected, state->order),
|
||||
st::premiumDotsMargin);
|
||||
}
|
||||
const auto showFinished = [=] {
|
||||
state->showFinished = true;
|
||||
if (base::take(state->preloadScheduled)) {
|
||||
state->preload();
|
||||
}
|
||||
};
|
||||
if ((descriptor.fromSettings && show->session().premium())
|
||||
if (gifts) {
|
||||
box->setShowFinishedCallback(showFinished);
|
||||
box->addButton(
|
||||
rpl::single(QString()),
|
||||
[=] { box->closeBox(); }
|
||||
)->setText(rpl::single(Ui::Text::IconEmoji(
|
||||
&st::infoStarsUnderstood
|
||||
).append(' ').append(tr::lng_auction_about_understood(tr::now))));
|
||||
} else if ((descriptor.fromSettings && show->session().premium())
|
||||
|| descriptor.hideSubscriptionButton) {
|
||||
box->setShowFinishedCallback(showFinished);
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
|
||||
@@ -74,6 +74,7 @@ enum class PremiumFeature {
|
||||
FilterTags,
|
||||
TodoLists,
|
||||
PeerColors,
|
||||
Gifts,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace {
|
||||
return true;
|
||||
};
|
||||
if (!updateThumbnail()) {
|
||||
document->owner().session().downloaderTaskFinished(
|
||||
document->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (updateThumbnail()) {
|
||||
state->loadingLifetime.destroy();
|
||||
|
||||
@@ -1731,14 +1731,14 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto &api = history->owner().session().api();
|
||||
auto &api = history->session().api();
|
||||
auto &histories = history->owner().histories();
|
||||
const auto donePhraseArgs = CreateForwardedMessagePhraseArgs(
|
||||
result,
|
||||
msgIds);
|
||||
const auto showRecentForwardsToSelf = result.size() == 1
|
||||
&& result.front()->peer()->isSelf()
|
||||
&& history->owner().session().premium();
|
||||
&& history->session().premium();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
for (const auto thread : result) {
|
||||
if (!comment.text.isEmpty()) {
|
||||
@@ -1776,7 +1776,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
: Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag())
|
||||
| (sublistPeer ? Flag::f_reply_to : Flag())
|
||||
| (options.suggest ? Flag::f_suggested_post : Flag());
|
||||
| (options.suggest ? Flag::f_suggested_post : Flag())
|
||||
| (options.effectId ? Flag::f_effect : Flag());
|
||||
threadHistory->sendRequestId = api.request(
|
||||
MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -1792,6 +1793,7 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
MTP_int(options.scheduleRepeatPeriod),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTP_long(options.effectId),
|
||||
MTP_int(videoTimestamp.value_or(0)),
|
||||
MTP_long(starsPaid),
|
||||
Api::SuggestToMTP(options.suggest)
|
||||
|
||||
@@ -13,6 +13,8 @@ class Show;
|
||||
|
||||
namespace Data {
|
||||
struct GiftAuctionState;
|
||||
struct ActiveAuctions;
|
||||
struct StarGift;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info::PeerGifts {
|
||||
@@ -48,6 +50,7 @@ struct AuctionBidBoxArgs {
|
||||
enum class AuctionButtonCountdownType {
|
||||
Join,
|
||||
Place,
|
||||
Preview,
|
||||
};
|
||||
void SetAuctionButtonCountdownText(
|
||||
not_null<RoundButton*> button,
|
||||
@@ -60,4 +63,18 @@ void AuctionAboutBox(
|
||||
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
|
||||
|
||||
@@ -137,6 +137,7 @@ constexpr auto kCrossfadeDuration = crl::time(400);
|
||||
constexpr auto kUpgradeDoneToastDuration = 4 * crl::time(1000);
|
||||
constexpr auto kGiftsPreloadTimeout = 3 * crl::time(1000);
|
||||
constexpr auto kResellPriceCacheLifetime = 60 * crl::time(1000);
|
||||
constexpr auto kGradientButtonBgOpacity = 0.6;
|
||||
|
||||
using namespace HistoryView;
|
||||
using namespace Info::PeerGifts;
|
||||
@@ -1732,6 +1733,49 @@ void CheckMaybeGiftLocked(
|
||||
.forceTon = star->forceTon,
|
||||
},
|
||||
Settings::CreditsEntryBoxStyleOverrides()));
|
||||
} else if (unique && star->mine && !peer->isSelf()) {
|
||||
if (ShowTransferGiftLater(window->uiShow(), unique)) {
|
||||
return;
|
||||
}
|
||||
const auto done = [=] {
|
||||
window->session().credits().load(true);
|
||||
window->showPeerHistory(peer);
|
||||
};
|
||||
if (state->transferRequested == unique) {
|
||||
return;
|
||||
}
|
||||
state->transferRequested = unique;
|
||||
const auto savedId = star->transferId;
|
||||
using Payments::CheckoutResult;
|
||||
const auto formReady = [=](
|
||||
uint64 formId,
|
||||
CreditsAmount price,
|
||||
std::optional<CheckoutResult> failure) {
|
||||
state->transferRequested = nullptr;
|
||||
if (!failure && !price.stars()) {
|
||||
LOG(("API Error: "
|
||||
"Bad transfer invoice currenct."));
|
||||
} else if (!failure
|
||||
|| *failure == CheckoutResult::Free) {
|
||||
unique->starsForTransfer = failure
|
||||
? 0
|
||||
: price.whole();
|
||||
ShowTransferToBox(
|
||||
window,
|
||||
peer,
|
||||
unique,
|
||||
savedId,
|
||||
done);
|
||||
} else if (*failure == CheckoutResult::Cancelled) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
RequestOurForm(
|
||||
window->uiShow(),
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId, unique),
|
||||
peer->input),
|
||||
formReady);
|
||||
} else if (star && star->resale) {
|
||||
const auto id = star->info.id;
|
||||
if (state->resaleRequestingId == id) {
|
||||
@@ -1781,49 +1825,6 @@ void CheckMaybeGiftLocked(
|
||||
}
|
||||
});
|
||||
CheckMaybeGiftLocked(window, star->info.id, ready);
|
||||
} else if (unique && star->mine && !peer->isSelf()) {
|
||||
if (ShowTransferGiftLater(window->uiShow(), unique)) {
|
||||
return;
|
||||
}
|
||||
const auto done = [=] {
|
||||
window->session().credits().load(true);
|
||||
window->showPeerHistory(peer);
|
||||
};
|
||||
if (state->transferRequested == unique) {
|
||||
return;
|
||||
}
|
||||
state->transferRequested = unique;
|
||||
const auto savedId = star->transferId;
|
||||
using Payments::CheckoutResult;
|
||||
const auto formReady = [=](
|
||||
uint64 formId,
|
||||
CreditsAmount price,
|
||||
std::optional<CheckoutResult> failure) {
|
||||
state->transferRequested = nullptr;
|
||||
if (!failure && !price.stars()) {
|
||||
LOG(("API Error: "
|
||||
"Bad transfer invoice currenct."));
|
||||
} else if (!failure
|
||||
|| *failure == CheckoutResult::Free) {
|
||||
unique->starsForTransfer = failure
|
||||
? 0
|
||||
: price.whole();
|
||||
ShowTransferToBox(
|
||||
window,
|
||||
peer,
|
||||
unique,
|
||||
savedId,
|
||||
done);
|
||||
} else if (*failure == CheckoutResult::Cancelled) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
RequestOurForm(
|
||||
window->uiShow(),
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
Api::InputSavedStarGiftId(savedId, unique),
|
||||
peer->input),
|
||||
formReady);
|
||||
} else if (star
|
||||
&& star->info.perUserTotal
|
||||
&& !star->info.perUserRemains) {
|
||||
@@ -2797,53 +2798,90 @@ void SetupResalePriceButton(
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride,
|
||||
rpl::producer<CreditsAmount> resalePrice,
|
||||
Fn<void()> resaleClick) {
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args) {
|
||||
const auto cover = container->add(object_ptr<RpWidget>(container));
|
||||
|
||||
struct Released {
|
||||
Released() : white(QColor(255, 255, 255)) {
|
||||
Released() : link(QColor(255, 255, 255)) {
|
||||
}
|
||||
|
||||
rpl::variable<TextWithEntities> subtitleText;
|
||||
style::owned_color white;
|
||||
std::optional<Ui::Premium::ColoredMiniStars> stars;
|
||||
style::owned_color link;
|
||||
style::FlatLabel st;
|
||||
PeerData *by = nullptr;
|
||||
QColor bg;
|
||||
QColor fg;
|
||||
};
|
||||
const auto released = cover->lifetime().make_state<Released>();
|
||||
released->st = st::uniqueGiftReleasedBy;
|
||||
released->st.palette.linkFg = released->white.color();
|
||||
released->st.palette.linkFg = released->link.color();
|
||||
|
||||
if (resalePrice) {
|
||||
const auto repaintedHook = args.repaintedHook;
|
||||
if (args.resalePrice) {
|
||||
auto background = rpl::duplicate(
|
||||
data
|
||||
) | rpl::map([=](const Data::UniqueGift &unique) {
|
||||
return unique.backdrop.patternColor;
|
||||
) | rpl::map([=](const UniqueGiftCover &cover) {
|
||||
auto result = cover.values.backdrop.patternColor;
|
||||
result.setAlphaF(kGradientButtonBgOpacity * result.alphaF());
|
||||
return result;
|
||||
});
|
||||
SetupResalePriceButton(
|
||||
cover,
|
||||
std::move(background),
|
||||
std::move(resalePrice),
|
||||
std::move(resaleClick));
|
||||
std::move(args.resalePrice),
|
||||
std::move(args.resaleClick));
|
||||
}
|
||||
|
||||
const auto pretitle = args.pretitle
|
||||
? CreateChild<FlatLabel>(
|
||||
cover,
|
||||
std::move(args.pretitle),
|
||||
st::uniqueGiftPretitle)
|
||||
: nullptr;
|
||||
if (pretitle) {
|
||||
released->stars.emplace(
|
||||
cover,
|
||||
true,
|
||||
Ui::Premium::MiniStarsType::SlowStars);
|
||||
const auto white = QColor(255, 255, 255);
|
||||
released->stars->setColorOverride(QGradientStops{
|
||||
{ 0., anim::with_alpha(white, .3) },
|
||||
{ 1., white },
|
||||
});
|
||||
pretitle->geometryValue() | rpl::start_with_next([=](QRect rect) {
|
||||
const auto half = rect.height() / 2;
|
||||
released->stars->setCenter(rect - QMargins(half, 0, half, 0));
|
||||
}, pretitle->lifetime());
|
||||
pretitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
pretitle->setTextColorOverride(QColor(255, 255, 255));
|
||||
pretitle->paintOn([=](QPainter &p) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = pretitle->height() / 2.;
|
||||
p.setPen(Qt::NoPen);
|
||||
auto bg = released->bg;
|
||||
bg.setAlphaF(kGradientButtonBgOpacity * bg.alphaF());
|
||||
p.setBrush(bg);
|
||||
p.drawRoundedRect(pretitle->rect(), radius, radius);
|
||||
p.translate(-pretitle->pos());
|
||||
released->stars->paint(p);
|
||||
});
|
||||
}
|
||||
const auto title = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
rpl::duplicate(
|
||||
data
|
||||
) | rpl::map([](const Data::UniqueGift &now) { return now.title; }),
|
||||
) | rpl::map([](const UniqueGiftCover &now) {
|
||||
return now.values.title;
|
||||
}),
|
||||
st::uniqueGiftTitle);
|
||||
title->setTextColorOverride(QColor(255, 255, 255));
|
||||
released->subtitleText = subtitleOverride
|
||||
? std::move(
|
||||
subtitleOverride
|
||||
) | Ui::Text::ToWithEntities() | rpl::type_erased()
|
||||
: rpl::duplicate(data) | rpl::map([=](const Data::UniqueGift &gift) {
|
||||
released->subtitleText = args.subtitle
|
||||
? std::move(args.subtitle)
|
||||
: rpl::duplicate(data) | rpl::map([=](const UniqueGiftCover &cover) {
|
||||
const auto &gift = cover.values;
|
||||
released->by = gift.releasedBy;
|
||||
released->bg = gift.backdrop.patternColor;
|
||||
return gift.releasedBy
|
||||
? tr::lng_gift_unique_number_by(
|
||||
tr::now,
|
||||
@@ -2860,13 +2898,18 @@ void AddUniqueGiftCover(
|
||||
});
|
||||
if (!released->by) {
|
||||
released->st = st::uniqueGiftSubtitle;
|
||||
released->st.palette.linkFg = released->white.color();
|
||||
released->st.palette.linkFg = released->link.color();
|
||||
}
|
||||
const auto subtitle = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
released->subtitleText.value(),
|
||||
released->st);
|
||||
if (released->by) {
|
||||
if (const auto handler = args.subtitleClick) {
|
||||
subtitle->setClickHandlerFilter([=](const auto &...) {
|
||||
handler();
|
||||
return false;
|
||||
});
|
||||
} else if (released->by) {
|
||||
const auto button = CreateChild<AbstractButton>(cover);
|
||||
subtitle->raise();
|
||||
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
@@ -2902,6 +2945,7 @@ void AddUniqueGiftCover(
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
std::unique_ptr<Text::CustomEmoji> emoji;
|
||||
base::flat_map<float64, QImage> emojis;
|
||||
bool forced = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
struct State {
|
||||
@@ -2909,25 +2953,44 @@ void AddUniqueGiftCover(
|
||||
GiftView next;
|
||||
Animations::Simple crossfade;
|
||||
bool animating = false;
|
||||
bool updateAttributesPending = false;
|
||||
};
|
||||
const auto state = cover->lifetime().make_state<State>();
|
||||
const auto lottieSize = st::creditsHistoryEntryStarGiftSize;
|
||||
const auto updateLinkFg = args.subtitleLinkColored;
|
||||
const auto updateColors = [=](float64 progress) {
|
||||
subtitle->setTextColorOverride((progress == 0.)
|
||||
if (repaintedHook) {
|
||||
repaintedHook(state->now.gift, state->next.gift, progress);
|
||||
}
|
||||
released->bg = (progress == 0.)
|
||||
? state->now.gift->backdrop.patternColor
|
||||
: (progress == 1.)
|
||||
? state->next.gift->backdrop.patternColor
|
||||
: anim::color(
|
||||
state->now.gift->backdrop.patternColor,
|
||||
state->next.gift->backdrop.patternColor,
|
||||
progress);
|
||||
const auto color = (progress == 0.)
|
||||
? state->now.gift->backdrop.textColor
|
||||
: (progress == 1.)
|
||||
? state->next.gift->backdrop.textColor
|
||||
: anim::color(
|
||||
state->now.gift->backdrop.textColor,
|
||||
state->next.gift->backdrop.textColor,
|
||||
progress));
|
||||
progress);
|
||||
if (updateLinkFg) {
|
||||
released->link.update(color);
|
||||
}
|
||||
released->fg = color;
|
||||
subtitle->setTextColorOverride(color);
|
||||
};
|
||||
std::move(
|
||||
rpl::duplicate(
|
||||
data
|
||||
) | rpl::start_with_next([=](const Data::UniqueGift &gift) {
|
||||
) | rpl::start_with_next([=](const UniqueGiftCover &now) {
|
||||
const auto setup = [&](GiftView &to) {
|
||||
to.gift = gift;
|
||||
const auto document = gift.model.document;
|
||||
to.gift = now.values;
|
||||
to.forced = now.force;
|
||||
const auto document = now.values.model.document;
|
||||
to.media = document->createMediaView();
|
||||
to.media->automaticLoad({}, nullptr);
|
||||
rpl::single() | rpl::then(
|
||||
@@ -2952,7 +3015,7 @@ void AddUniqueGiftCover(
|
||||
}, to.lifetime);
|
||||
}, to.lifetime);
|
||||
to.emoji = document->owner().customEmojiManager().create(
|
||||
gift.pattern.document,
|
||||
now.values.pattern.document,
|
||||
[=] { cover->update(); },
|
||||
Data::CustomEmojiSizeTag::Large);
|
||||
[[maybe_unused]] const auto preload = to.emoji->ready();
|
||||
@@ -2962,11 +3025,122 @@ void AddUniqueGiftCover(
|
||||
setup(state->now);
|
||||
cover->update();
|
||||
updateColors(0.);
|
||||
} else if (!state->next.gift) {
|
||||
} else if (!state->next.gift || now.force) {
|
||||
setup(state->next);
|
||||
}
|
||||
}, cover->lifetime());
|
||||
|
||||
const auto attrs = args.attributesInfo
|
||||
? CreateChild<RpWidget>(cover)
|
||||
: nullptr;
|
||||
auto updateAttrs = Fn<void(const Data::UniqueGift &)>([](const auto &) {
|
||||
});
|
||||
if (attrs) {
|
||||
struct AttributeState {
|
||||
Ui::Text::String name;
|
||||
Ui::Text::String type;
|
||||
Ui::Text::String percent;
|
||||
};
|
||||
struct AttributesState {
|
||||
AttributeState model;
|
||||
AttributeState pattern;
|
||||
AttributeState backdrop;
|
||||
};
|
||||
const auto astate = cover->lifetime().make_state<AttributesState>();
|
||||
const auto setType = [&](AttributeState &state, tr::phrase<> text) {
|
||||
state.type = Ui::Text::String(
|
||||
st::uniqueAttributeType,
|
||||
text(tr::now));
|
||||
};
|
||||
setType(astate->model, tr::lng_auction_preview_model);
|
||||
setType(astate->pattern, tr::lng_auction_preview_symbol);
|
||||
setType(astate->backdrop, tr::lng_auction_preview_backdrop);
|
||||
|
||||
updateAttrs = [=](const Data::UniqueGift &gift) {
|
||||
const auto set = [&](
|
||||
AttributeState &state,
|
||||
const Data::UniqueGiftAttribute &value) {
|
||||
state.name = Ui::Text::String(
|
||||
st::uniqueAttributeName,
|
||||
value.name);
|
||||
state.percent = Ui::Text::String(
|
||||
st::uniqueAttributePercent,
|
||||
QString::number(value.rarityPermille / 10.) + '%');
|
||||
};
|
||||
set(astate->model, gift.model);
|
||||
set(astate->pattern, gift.pattern);
|
||||
set(astate->backdrop, gift.backdrop);
|
||||
attrs->update();
|
||||
};
|
||||
const auto height = st::uniqueAttributeTop
|
||||
+ st::uniqueAttributePadding.top()
|
||||
+ st::uniqueAttributeName.font->height
|
||||
+ st::uniqueAttributeType.font->height
|
||||
+ st::uniqueAttributePadding.bottom();
|
||||
attrs->resize(attrs->width(), height);
|
||||
attrs->paintOn([=](QPainter &p) {
|
||||
const auto skip = st::giftBoxGiftSkip.x();
|
||||
const auto available = attrs->width() - 2 * skip;
|
||||
const auto single = available / 3;
|
||||
if (single <= 0) {
|
||||
return;
|
||||
}
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto bg = released->bg;
|
||||
bg.setAlphaF(kGradientButtonBgOpacity * bg.alphaF());
|
||||
const auto innert = st::uniqueAttributeTop;
|
||||
const auto innerh = height - innert;
|
||||
const auto radius = innerh / 3.;
|
||||
const auto paint = [&](int x, const AttributeState &state) {
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(bg);
|
||||
p.drawRoundedRect(x, innert, single, innerh, radius, radius);
|
||||
p.setPen(QColor(255, 255, 255));
|
||||
const auto padding = st::uniqueAttributePadding;
|
||||
const auto inner = single - padding.left() - padding.right();
|
||||
const auto namew = std::min(inner, state.name.maxWidth());
|
||||
state.name.draw(p, {
|
||||
.position = QPoint(
|
||||
x + (single - namew) / 2,
|
||||
innert + padding.top()),
|
||||
.availableWidth = namew,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
p.setPen(released->fg);
|
||||
const auto typew = std::min(inner, state.type.maxWidth());
|
||||
state.type.draw(p, {
|
||||
.position = QPoint(
|
||||
x + (single - typew) / 2,
|
||||
innert + padding.top() + state.name.minHeight()),
|
||||
.availableWidth = typew,
|
||||
});
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(anim::color(released->bg, released->fg, 0.3));
|
||||
const auto r = st::uniqueAttributePercent.font->height / 2.;
|
||||
const auto left = x + single - state.percent.maxWidth();
|
||||
const auto top = st::uniqueAttributePercentPadding.top();
|
||||
const auto percent = QRect(
|
||||
left,
|
||||
top,
|
||||
state.percent.maxWidth(),
|
||||
st::uniqueAttributeType.font->height);
|
||||
p.drawRoundedRect(
|
||||
percent.marginsAdded(st::uniqueAttributePercentPadding),
|
||||
r,
|
||||
r);
|
||||
p.setPen(QColor(255, 255, 255));
|
||||
state.percent.draw(p, {
|
||||
.position = percent.topLeft(),
|
||||
});
|
||||
};
|
||||
auto left = 0;
|
||||
paint(left, astate->model);
|
||||
paint(left + single + skip, astate->backdrop);
|
||||
paint(attrs->width() - single - left, astate->pattern);
|
||||
});
|
||||
}
|
||||
updateAttrs(*state->now.gift);
|
||||
|
||||
cover->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto skip = st::uniqueGiftBottom;
|
||||
if (width <= 3 * skip) {
|
||||
@@ -2974,18 +3148,52 @@ void AddUniqueGiftCover(
|
||||
}
|
||||
const auto available = width - 2 * skip;
|
||||
title->resizeToWidth(available);
|
||||
title->moveToLeft(skip, st::uniqueGiftTitleTop);
|
||||
|
||||
subtitle->resizeToWidth(available);
|
||||
subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
|
||||
|
||||
cover->resize(width, subtitle->y() + subtitle->height() + skip);
|
||||
auto top = st::uniqueGiftTitleTop;
|
||||
if (pretitle) {
|
||||
pretitle->move((width - pretitle->width()) / 2, top);
|
||||
top += pretitle->height()
|
||||
+ (st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop)
|
||||
- title->height();
|
||||
}
|
||||
|
||||
title->moveToLeft(skip, top);
|
||||
if (pretitle) {
|
||||
top += title->height() + st::defaultVerticalListSkip;
|
||||
} else {
|
||||
top += st::uniqueGiftSubtitleTop - st::uniqueGiftTitleTop;
|
||||
}
|
||||
|
||||
subtitle->moveToLeft(skip, top);
|
||||
top += subtitle->height() + (skip / 2);
|
||||
|
||||
if (attrs) {
|
||||
attrs->resizeToWidth(width
|
||||
- st::giftBoxPadding.left()
|
||||
- st::giftBoxPadding.right());
|
||||
attrs->moveToLeft(st::giftBoxPadding.left(), top);
|
||||
top += attrs->height() + (skip / 2);
|
||||
} else {
|
||||
top += (skip / 2);
|
||||
}
|
||||
|
||||
cover->resize(width, top);
|
||||
}, cover->lifetime());
|
||||
|
||||
cover->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(cover);
|
||||
|
||||
auto progress = state->crossfade.value(state->animating ? 1. : 0.);
|
||||
if (state->updateAttributesPending && progress >= 0.5) {
|
||||
state->updateAttributesPending = false;
|
||||
updateAttrs(*state->next.gift);
|
||||
} else if (state->updateAttributesPending
|
||||
&& !state->animating
|
||||
&& !state->crossfade.animating()) {
|
||||
state->updateAttributesPending = false;
|
||||
updateAttrs(*state->now.gift);
|
||||
}
|
||||
if (state->animating) {
|
||||
updateColors(progress);
|
||||
}
|
||||
@@ -3007,15 +3215,16 @@ void AddUniqueGiftCover(
|
||||
}
|
||||
p.drawImage(0, 0, gift.gradient);
|
||||
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternBgPoints(),
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
shown);
|
||||
|
||||
if (gift.gift->pattern.document != gift.gift->model.document) {
|
||||
Ui::PaintBgPoints(
|
||||
p,
|
||||
Ui::PatternBgPoints(),
|
||||
gift.emojis,
|
||||
gift.emoji.get(),
|
||||
*gift.gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
shown);
|
||||
}
|
||||
const auto lottie = gift.lottie.get();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto request = Lottie::FrameRequest{
|
||||
@@ -3039,10 +3248,13 @@ void AddUniqueGiftCover(
|
||||
};
|
||||
|
||||
if (progress < 1.) {
|
||||
const auto finished = paint(state->now, 1. - progress);
|
||||
const auto finished = paint(state->now, 1. - progress)
|
||||
|| (state->next.forced
|
||||
&& (!state->animating || !state->crossfade.animating()));
|
||||
const auto next = finished ? state->next.lottie.get() : nullptr;
|
||||
if (next && next->ready()) {
|
||||
state->animating = true;
|
||||
state->updateAttributesPending = true;
|
||||
state->crossfade.start([=] {
|
||||
cover->update();
|
||||
}, 0., 1., kCrossfadeDuration);
|
||||
@@ -3201,10 +3413,12 @@ void ShowUniqueGiftWearBox(
|
||||
? tr::lng_gift_wear_badge_about_channel()
|
||||
: tr::lng_gift_wear_badge_about()),
|
||||
st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
|
||||
//infoRow(
|
||||
// tr::lng_gift_wear_design_title(),
|
||||
// tr::lng_gift_wear_design_about(),
|
||||
// &st::menuIconUniqueProfile);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_design_title(),
|
||||
(channel
|
||||
? tr::lng_gift_wear_design_about_channel()
|
||||
: tr::lng_gift_wear_design_about()),
|
||||
st.profileIcon ? st.profileIcon : &st::menuIconUniqueProfile);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_proof_title(),
|
||||
(channel
|
||||
@@ -3560,6 +3774,155 @@ void ShowUniqueGiftSellBox(
|
||||
});
|
||||
}
|
||||
|
||||
void SendOfferBuyGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> unique,
|
||||
SuggestOptions options,
|
||||
int starsPerMessage,
|
||||
Fn<void(bool)> done) {
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto owner = show->session().data().peer(unique->ownerId);
|
||||
|
||||
using Flag = MTPpayments_SendStarGiftOffer::Flag;
|
||||
show->session().api().request(MTPpayments_SendStarGiftOffer(
|
||||
MTP_flags(starsPerMessage ? Flag::f_allow_paid_stars : Flag()),
|
||||
owner->input,
|
||||
MTP_string(unique->slug),
|
||||
StarsAmountToTL(options.price()),
|
||||
MTP_int(options.offerDuration),
|
||||
MTP_long(randomId),
|
||||
MTP_long(starsPerMessage)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
show->session().api().applyUpdates(result);
|
||||
done(true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u""_q) {
|
||||
} else {
|
||||
show->showToast(error.type());
|
||||
}
|
||||
done(false);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ConfirmOfferBuyGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> unique,
|
||||
SuggestOptions options,
|
||||
Fn<void()> done) {
|
||||
const auto owner = show->session().data().peer(unique->ownerId);
|
||||
const auto fee = owner->starsPerMessageChecked();
|
||||
const auto price = options.price();
|
||||
const auto sent = std::make_shared<bool>();
|
||||
const auto send = [=](Fn<void()> close) {
|
||||
if (*sent) {
|
||||
return;
|
||||
}
|
||||
*sent = true;
|
||||
SendOfferBuyGift(show, unique, options, fee, [=](bool ok) {
|
||||
*sent = false;
|
||||
if (ok) {
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
window->showPeerHistory(owner->id);
|
||||
}
|
||||
done();
|
||||
close();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = tr::lng_gift_offer_confirm_text(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
tr::bold(PrepareCreditsAmountText(options.price())),
|
||||
lt_user,
|
||||
tr::bold(owner->shortName()),
|
||||
lt_name,
|
||||
tr::bold(Data::UniqueGiftName(*unique)),
|
||||
tr::marked),
|
||||
.confirmed = send,
|
||||
.confirmText = tr::lng_payments_pay_amount(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
Ui::Text::IconEmoji(price.ton()
|
||||
? &st::buttonTonIconEmoji
|
||||
: &st::buttonStarIconEmoji
|
||||
).append(Lang::FormatCreditsAmountDecimal(price.ton()
|
||||
? price
|
||||
: CreditsAmount(price.whole() + fee))),
|
||||
tr::marked),
|
||||
.title = tr::lng_gift_offer_confirm_title(),
|
||||
});
|
||||
|
||||
auto helper = Ui::Text::CustomEmojiHelper();
|
||||
const auto starIcon = helper.paletteDependent(
|
||||
Ui::Earn::IconCreditsEmoji());
|
||||
const auto tonIcon = helper.paletteDependent(
|
||||
Ui::Earn::IconCurrencyEmoji());
|
||||
const auto context = helper.context();
|
||||
const auto table = box->addRow(
|
||||
object_ptr<Ui::TableLayout>(box, st::defaultTable),
|
||||
st::boxPadding);
|
||||
const auto add = [&](tr::phrase<> label, TextWithEntities value) {
|
||||
table->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
label(),
|
||||
st::defaultTable.defaultLabel),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(value),
|
||||
st::defaultTable.defaultValue,
|
||||
st::defaultPopupMenu,
|
||||
context),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
};
|
||||
add(tr::lng_gift_offer_table_offer, tr::marked(price.ton()
|
||||
? tonIcon
|
||||
: starIcon).append(Lang::FormatCreditsAmountDecimal(price)));
|
||||
if (fee) {
|
||||
add(tr::lng_gift_offer_table_fee, tr::marked(starIcon).append(
|
||||
Lang::FormatCreditsAmountDecimal(CreditsAmount(fee))));
|
||||
}
|
||||
const auto hours = options.offerDuration / 3600;
|
||||
const auto duration = hours
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: tr::lng_minutes(tr::now, lt_count, options.offerDuration / 60);
|
||||
add(tr::lng_gift_offer_table_duration, tr::marked(duration));
|
||||
}));
|
||||
}
|
||||
|
||||
void ShowOfferBuyBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> unique) {
|
||||
Expects(unique->starsMinOffer >= 0);
|
||||
|
||||
const auto weak = std::make_shared<base::weak_qptr<Ui::BoxContent>>();
|
||||
const auto done = [=](SuggestOptions result) {
|
||||
ConfirmOfferBuyGift(show, unique, result, [=] {
|
||||
if (const auto strong = weak->get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
};
|
||||
using namespace HistoryView;
|
||||
const auto options = SuggestOptions{
|
||||
.exists = 1,
|
||||
.priceWhole = uint32(unique->starsMinOffer),
|
||||
};
|
||||
auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
|
||||
.peer = show->session().data().peer(unique->ownerId),
|
||||
.done = done,
|
||||
.value = options,
|
||||
.mode = SuggestMode::Gift,
|
||||
.giftName = UniqueGiftName(*unique),
|
||||
});
|
||||
*weak = priceBox.data();
|
||||
show->show(std::move(priceBox));
|
||||
}
|
||||
|
||||
void GiftReleasedByHandler(not_null<PeerData*> peer) {
|
||||
const auto session = &peer->session();
|
||||
const auto window = session->tryResolveWindow(peer);
|
||||
@@ -3586,12 +3949,12 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
std::vector<UpgradePrice> nextPrices;
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<Data::UniqueGift> MakeUpgradeGiftStream(
|
||||
[[nodiscard]] rpl::producer<UniqueGiftCover> MakeUpgradeGiftStream(
|
||||
const UpgradeArgs &args) {
|
||||
if (args.models.empty()
|
||||
|| args.patterns.empty()
|
||||
|| args.backdrops.empty()) {
|
||||
return rpl::never<Data::UniqueGift>();
|
||||
return rpl::never<UniqueGiftCover>();
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
@@ -3629,14 +3992,14 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
auto &models = state->data.models;
|
||||
auto &patterns = state->data.patterns;
|
||||
auto &backdrops = state->data.backdrops;
|
||||
consumer.put_next(Data::UniqueGift{
|
||||
consumer.put_next(UniqueGiftCover{ Data::UniqueGift{
|
||||
.title = (state->data.savedId
|
||||
? tr::lng_gift_upgrade_title(tr::now)
|
||||
: tr::lng_gift_upgrade_preview_title(tr::now)),
|
||||
.model = models[index(state->modelIndices, models)],
|
||||
.pattern = patterns[index(state->patternIndices, patterns)],
|
||||
.backdrop = backdrops[index(state->backdropIndices, backdrops)],
|
||||
});
|
||||
} });
|
||||
};
|
||||
|
||||
put();
|
||||
@@ -3651,16 +4014,16 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
void AddUpgradeGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const UpgradeArgs &args) {
|
||||
AddUniqueGiftCover(
|
||||
container,
|
||||
MakeUpgradeGiftStream(args),
|
||||
(args.savedId
|
||||
? tr::lng_gift_upgrade_about()
|
||||
AddUniqueGiftCover(container, MakeUpgradeGiftStream(args), {
|
||||
.subtitle = (args.savedId
|
||||
? tr::lng_gift_upgrade_about(tr::marked)
|
||||
: (args.peer->isBroadcast()
|
||||
? tr::lng_gift_upgrade_preview_about_channel
|
||||
: tr::lng_gift_upgrade_preview_about)(
|
||||
lt_name,
|
||||
rpl::single(args.peer->shortName()))));
|
||||
rpl::single(tr::marked(args.peer->shortName())),
|
||||
tr::marked)),
|
||||
});
|
||||
}
|
||||
|
||||
class UpgradePriceValue final {
|
||||
@@ -4409,8 +4772,8 @@ CreditsAmount StarsFromTon(
|
||||
not_null<Main::Session*> session,
|
||||
CreditsAmount ton) {
|
||||
const auto appConfig = &session->appConfig();
|
||||
const auto starsRate = appConfig->starsWithdrawRate() / 100.;
|
||||
const auto tonRate = appConfig->currencyWithdrawRate();
|
||||
const auto starsRate = appConfig->starsSellRate() / 100.;
|
||||
const auto tonRate = appConfig->currencySellRate();
|
||||
if (!starsRate) {
|
||||
return {};
|
||||
}
|
||||
@@ -4422,8 +4785,8 @@ CreditsAmount TonFromStars(
|
||||
not_null<Main::Session*> session,
|
||||
CreditsAmount stars) {
|
||||
const auto appConfig = &session->appConfig();
|
||||
const auto starsRate = appConfig->starsWithdrawRate() / 100.;
|
||||
const auto tonRate = appConfig->currencyWithdrawRate();
|
||||
const auto starsRate = appConfig->starsSellRate() / 100.;
|
||||
const auto tonRate = appConfig->currencySellRate();
|
||||
if (!tonRate) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -65,12 +65,28 @@ void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
struct UniqueGiftCoverArgs {
|
||||
rpl::producer<QString> pretitle;
|
||||
rpl::producer<TextWithEntities> subtitle;
|
||||
Fn<void()> subtitleClick;
|
||||
bool subtitleLinkColored = false;
|
||||
rpl::producer<CreditsAmount> resalePrice;
|
||||
Fn<void()> resaleClick;
|
||||
bool attributesInfo = false;
|
||||
Fn<void(
|
||||
std::optional<Data::UniqueGift> now,
|
||||
std::optional<Data::UniqueGift> next,
|
||||
float64 progress)> repaintedHook;
|
||||
};
|
||||
struct UniqueGiftCover {
|
||||
Data::UniqueGift values;
|
||||
bool force = false;
|
||||
};
|
||||
|
||||
void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr,
|
||||
rpl::producer<CreditsAmount> resalePrice = nullptr,
|
||||
Fn<void()> resaleClick = nullptr);
|
||||
rpl::producer<UniqueGiftCover> data,
|
||||
UniqueGiftCoverArgs &&args);
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
@@ -95,6 +111,10 @@ void ShowUniqueGiftSellBox(
|
||||
Data::SavedStarGiftId savedId,
|
||||
Settings::GiftWearBoxStyleOverride st);
|
||||
|
||||
void ShowOfferBuyBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> unique);
|
||||
|
||||
void GiftReleasedByHandler(not_null<PeerData*> peer);
|
||||
|
||||
struct StarGiftUpgradeArgs {
|
||||
|
||||
1420
Telegram/SourceFiles/boxes/star_gift_preview_box.cpp
Normal file
29
Telegram/SourceFiles/boxes/star_gift_preview_box.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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 StarGift;
|
||||
struct UniqueGiftAttributes;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void StarGiftPreviewBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::StarGift &gift,
|
||||
const Data::UniqueGiftAttributes &attributes);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -1548,7 +1548,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox(
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->requestId = document->owner().session().api().request(
|
||||
state->requestId = document->session().api().request(
|
||||
MTPstickers_RemoveStickerFromSet(document->mtpInput()
|
||||
)).done([=](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
@@ -1590,7 +1590,7 @@ void StickerSetBox::Inner::fillDeleteStickerBox(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
|
||||
}
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
document->owner().session().api().request(
|
||||
document->session().api().request(
|
||||
state->requestId.current()).cancel();
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
@@ -24,13 +24,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_star_gift.h"
|
||||
#include "data/data_thread.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/sub_tabs.h"
|
||||
#include "ui/controls/ton_common.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
@@ -492,6 +497,26 @@ void TransferGift(
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveGiftSaleOffer(
|
||||
not_null<Window::SessionController*> window,
|
||||
MsgId id,
|
||||
bool accept,
|
||||
Fn<void(bool)> done) {
|
||||
using Flag = MTPpayments_ResolveStarGiftOffer::Flag;
|
||||
const auto session = &window->session();
|
||||
const auto show = window->uiShow();
|
||||
session->api().request(MTPpayments_ResolveStarGiftOffer(
|
||||
MTP_flags(accept ? Flag() : Flag::f_decline),
|
||||
MTP_int(id.bare)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
done(true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
done(false);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void BuyResaleGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> to,
|
||||
@@ -687,6 +712,164 @@ void ShowTransferGiftBox(
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
void ShowGiftSaleAcceptBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion) {
|
||||
const auto id = item->id;
|
||||
const auto peer = item->history()->peer;
|
||||
const auto gift = suggestion->gift;
|
||||
const auto price = suggestion->price;
|
||||
|
||||
const auto &appConfig = controller->session().appConfig();
|
||||
const auto starsThousandths = appConfig.giftResaleStarsThousandths();
|
||||
const auto nanoTonThousandths = appConfig.giftResaleNanoTonThousandths();
|
||||
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
struct State {
|
||||
bool sent = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
auto callback = [=] {
|
||||
if (state->sent) {
|
||||
return;
|
||||
}
|
||||
state->sent = true;
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto weakBox = base::make_weak(box);
|
||||
ResolveGiftSaleOffer(controller, id, true, [=](bool ok) {
|
||||
state->sent = false;
|
||||
if (ok) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showPeerHistory(peer->id);
|
||||
}
|
||||
if (const auto strong = weakBox.get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto receive = price.ton()
|
||||
? ((price.value() * nanoTonThousandths) / 1000.)
|
||||
: ((int64(price.value()) * starsThousandths) / 1000);
|
||||
|
||||
auto button = tr::lng_gift_offer_sell_for(
|
||||
lt_price,
|
||||
rpl::single(Ui::Text::IconEmoji(price.ton()
|
||||
? &st::buttonTonIconEmoji
|
||||
: &st::buttonStarIconEmoji
|
||||
).append(Lang::FormatExactCountDecimal(receive))),
|
||||
tr::marked);
|
||||
|
||||
box->addRow(
|
||||
CreateGiftTransfer(box->verticalLayout(), gift, peer),
|
||||
QMargins(0, st::boxPadding.top(), 0, 0));
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = tr::lng_gift_offer_confirm_accept(
|
||||
tr::now,
|
||||
lt_name,
|
||||
tr::bold(UniqueGiftName(*gift)),
|
||||
lt_user,
|
||||
tr::bold(peer->shortName()),
|
||||
lt_cost,
|
||||
tr::bold(PrepareCreditsAmountText(price)),
|
||||
tr::marked
|
||||
).append(u"\n\n"_q).append(tr::lng_gift_offer_you_get(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
tr::bold(price.stars()
|
||||
? tr::lng_action_gift_for_stars(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
receive)
|
||||
: tr::lng_action_gift_for_ton(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
receive)),
|
||||
tr::marked)),
|
||||
.confirmed = std::move(callback),
|
||||
.confirmText = std::move(button),
|
||||
});
|
||||
|
||||
const auto show = controller->uiShow();
|
||||
auto taken = base::take(gift->value);
|
||||
AddTransferGiftTable(show, box->verticalLayout(), gift);
|
||||
gift->value = std::move(taken);
|
||||
|
||||
if (gift->value.get()) {
|
||||
const auto appConfig = &show->session().appConfig();
|
||||
const auto rule = Ui::LookupCurrencyRule(u"USD"_q);
|
||||
const auto value = (gift->value->valuePriceUsd > 0 ? 1 : -1)
|
||||
* std::abs(gift->value->valuePriceUsd)
|
||||
/ std::pow(10., rule.exponent);
|
||||
if (std::abs(value) >= 0.01) {
|
||||
const auto rate = price.ton()
|
||||
? appConfig->currencySellRate()
|
||||
: (appConfig->starsSellRate() / 100.);
|
||||
const auto offered = receive * rate;
|
||||
const auto diff = offered - value;
|
||||
const auto percent = std::abs(diff / value * 100.);
|
||||
if (percent >= 1) {
|
||||
const auto higher = (diff > 0.);
|
||||
const auto good = higher || (percent < 10);
|
||||
const auto number = int(base::SafeRound(percent));
|
||||
const auto percentText = QString::number(number) + '%';
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
(higher
|
||||
? tr::lng_gift_offer_higher
|
||||
: tr::lng_gift_offer_lower)(
|
||||
lt_percent,
|
||||
rpl::single(tr::bold(percentText)),
|
||||
lt_name,
|
||||
rpl::single(tr::marked(gift->title)),
|
||||
tr::marked),
|
||||
(good ? st::offerValueGood : st::offerValueBad)),
|
||||
st::boxRowPadding + st::offerValuePadding
|
||||
)->setTryMakeSimilarLines(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void ShowGiftSaleRejectBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion) {
|
||||
struct State {
|
||||
bool sent = false;
|
||||
};
|
||||
const auto id = item->id;
|
||||
const auto state = std::make_shared<State>();
|
||||
auto callback = [=](Fn<void()> close) {
|
||||
if (state->sent) {
|
||||
return;
|
||||
}
|
||||
state->sent = true;
|
||||
const auto weak = base::make_weak(controller);
|
||||
ResolveGiftSaleOffer(controller, id, false, [=](bool ok) {
|
||||
state->sent = false;
|
||||
if (ok) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
};
|
||||
controller->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_gift_offer_confirm_reject(
|
||||
lt_user,
|
||||
rpl::single(tr::bold(item->history()->peer->shortName())),
|
||||
tr::marked),
|
||||
.confirmed = std::move(callback),
|
||||
.confirmText = tr::lng_action_gift_offer_decline(),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
.title = tr::lng_gift_offer_reject_title(),
|
||||
}));
|
||||
}
|
||||
|
||||
void SetThemeFromUniqueGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> unique) {
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
struct HistoryMessageSuggestion;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -36,6 +38,15 @@ void ShowTransferGiftBox(
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId savedId);
|
||||
|
||||
void ShowGiftSaleAcceptBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion);
|
||||
void ShowGiftSaleRejectBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessageSuggestion*> suggestion);
|
||||
|
||||
void ShowBuyResaleGiftBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
|
||||
@@ -1698,6 +1698,7 @@ groupCallMessagesScroll: ScrollArea(defaultScrollArea) {
|
||||
barHidden: true;
|
||||
}
|
||||
groupCallMessagePadding: margins(8px, 3px, 8px, 2px);
|
||||
groupCallPricePadding: margins(5px, 0px, 5px, 0px);
|
||||
groupCallMessageSkip: 8px;
|
||||
groupCallMessagePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: radialFg;
|
||||
@@ -1721,6 +1722,8 @@ groupCallUserpicPadding: margins(2px, 2px, 4px, 2px);
|
||||
groupCallPinnedPadding: margins(10px, 4px, 10px, 2px);
|
||||
groupCallPinnedMaxWidth: 96px;
|
||||
groupCallPinnedUserpic: 22px;
|
||||
groupCallEffectPadding: margins(3px, 1px, 3px, 1px);
|
||||
groupCallEffectUserpicPadding: margins(1px, 1px, 3px, 1px);
|
||||
|
||||
groupCallTopReactorBadge: RoundCheckbox(defaultRoundCheckbox) {
|
||||
width: 1px;
|
||||
|
||||
@@ -554,6 +554,7 @@ void SetupFingerprintTooltip(not_null<Ui::RpWidget*> widget) {
|
||||
std::unique_ptr<Ui::ImportantTooltip> tooltip;
|
||||
Fn<void()> updateGeometry;
|
||||
Fn<void(bool)> toggleTooltip;
|
||||
bool tooltipShown = false;
|
||||
};
|
||||
const auto state = widget->lifetime().make_state<State>();
|
||||
state->updateGeometry = [=] {
|
||||
@@ -617,9 +618,22 @@ 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.
|
||||
state->tooltipShown = true;
|
||||
crl::on_main(widget, [=] {
|
||||
if (state->tooltipShown) {
|
||||
state->toggleTooltip(true);
|
||||
}
|
||||
});
|
||||
} else if (type == QEvent::Leave) {
|
||||
state->toggleTooltip(false);
|
||||
state->tooltipShown = false;
|
||||
crl::on_main(widget, [=] {
|
||||
if (!state->tooltipShown) {
|
||||
state->toggleTooltip(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
@@ -69,12 +69,6 @@ constexpr auto kStarsStatsShortPollDelay = 30 * crl::time(1000);
|
||||
return PinFinishDate(message.peer, message.date, message.stars);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<PeerId> MaybeShownPeer(
|
||||
uint32 privacySet,
|
||||
PeerId shownPeer) {
|
||||
return privacySet ? shownPeer : std::optional<PeerId>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
|
||||
@@ -104,9 +98,7 @@ Messages::~Messages() {
|
||||
finishPaidSending({
|
||||
.count = int(_paid.sending),
|
||||
.valid = true,
|
||||
.shownPeer = MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer),
|
||||
.shownPeer = _paid.sendingShownPeer,
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
@@ -517,26 +509,23 @@ PeerId Messages::reactionsLocalShownPeer() const {
|
||||
return entry.peer ? entry.peer->id : PeerId();
|
||||
}
|
||||
}
|
||||
return _session->userPeerId();
|
||||
return _call->messagesFrom()->id;
|
||||
//const auto api = &_session->api();
|
||||
//return api->globalPrivacy().paidReactionShownPeerCurrent();
|
||||
};
|
||||
return (_paid.scheduledFlag && _paid.scheduledPrivacySet)
|
||||
return _paid.scheduledFlag
|
||||
? _paid.scheduledShownPeer
|
||||
: (_paid.sendingFlag && _paid.sendingPrivacySet)
|
||||
: _paid.sendingFlag
|
||||
? _paid.sendingShownPeer
|
||||
: minePaidShownPeer();
|
||||
}
|
||||
|
||||
void Messages::reactionsPaidAdd(int count, std::optional<PeerId> shownPeer) {
|
||||
void Messages::reactionsPaidAdd(int count) {
|
||||
Expects(count >= 0);
|
||||
|
||||
_paid.scheduled += count;
|
||||
_paid.scheduledFlag = 1;
|
||||
if (shownPeer.has_value()) {
|
||||
_paid.scheduledShownPeer = *shownPeer;
|
||||
_paid.scheduledPrivacySet = true;
|
||||
}
|
||||
_paid.scheduledShownPeer = _call->messagesFrom()->id;
|
||||
if (count > 0) {
|
||||
_session->credits().lock(CreditsAmount(count));
|
||||
}
|
||||
@@ -555,7 +544,6 @@ void Messages::reactionsPaidScheduledCancel() {
|
||||
_paid.scheduled = 0;
|
||||
_paid.scheduledFlag = 0;
|
||||
_paid.scheduledShownPeer = 0;
|
||||
_paid.scheduledPrivacySet = 0;
|
||||
_paidChanges.fire({});
|
||||
}
|
||||
|
||||
@@ -570,17 +558,13 @@ Data::PaidReactionSend Messages::startPaidReactionSending() {
|
||||
_paid.sending = _paid.scheduled;
|
||||
_paid.sendingFlag = _paid.scheduledFlag;
|
||||
_paid.sendingShownPeer = _paid.scheduledShownPeer;
|
||||
_paid.sendingPrivacySet = _paid.scheduledPrivacySet;
|
||||
_paid.scheduled = 0;
|
||||
_paid.scheduledFlag = 0;
|
||||
_paid.scheduledShownPeer = 0;
|
||||
_paid.scheduledPrivacySet = 0;
|
||||
return {
|
||||
.count = int(_paid.sending),
|
||||
.valid = true,
|
||||
.shownPeer = MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer),
|
||||
.shownPeer = _paid.sendingShownPeer,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -589,25 +573,24 @@ void Messages::finishPaidSending(
|
||||
bool success) {
|
||||
Expects(send.count == _paid.sending);
|
||||
Expects(send.valid == (_paid.sendingFlag == 1));
|
||||
Expects(send.shownPeer == MaybeShownPeer(
|
||||
_paid.sendingPrivacySet,
|
||||
_paid.sendingShownPeer));
|
||||
Expects(send.shownPeer == _paid.sendingShownPeer);
|
||||
|
||||
_paid.sending = 0;
|
||||
_paid.sendingFlag = 0;
|
||||
_paid.sendingShownPeer = 0;
|
||||
_paid.sendingPrivacySet = 0;
|
||||
if (const auto amount = send.count) {
|
||||
if (success) {
|
||||
const auto from = _session->data().peer(*send.shownPeer);
|
||||
_session->credits().withdrawLocked(CreditsAmount(amount));
|
||||
|
||||
auto &donors = _paid.top.topDonors;
|
||||
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
|
||||
const auto i = ranges::find(donors, true, &StarsDonor::my);
|
||||
if (i != end(donors)) {
|
||||
i->peer = from;
|
||||
i->stars += amount;
|
||||
} else {
|
||||
donors.push_back({
|
||||
.peer = _session->user(),
|
||||
.peer = from,
|
||||
.stars = amount,
|
||||
.my = true,
|
||||
});
|
||||
@@ -632,7 +615,7 @@ void Messages::reactionsPaidSend() {
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
_sendingIdByRandomId.emplace(randomId, localId);
|
||||
|
||||
const auto from = _call->messagesFrom();
|
||||
const auto from = _session->data().peer(*send.shownPeer);
|
||||
const auto stars = int(send.count);
|
||||
const auto skip = skipMessage({}, stars);
|
||||
if (skip) {
|
||||
@@ -676,7 +659,7 @@ void Messages::undoScheduledPaidOnDestroy() {
|
||||
|
||||
Messages::PaidLocalState Messages::starsLocalState() const {
|
||||
const auto &donors = _paid.top.topDonors;
|
||||
const auto i = ranges::find(donors, true, &StarsTopDonor::my);
|
||||
const auto i = ranges::find(donors, true, &StarsDonor::my);
|
||||
const auto local = int(_paid.scheduled);
|
||||
const auto my = (i != end(donors) ? i->stars : 0) + local;
|
||||
const auto total = _paid.top.total + local;
|
||||
@@ -728,7 +711,7 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
|
||||
const auto i = ranges::find(
|
||||
_paid.top.topDonors,
|
||||
from.get(),
|
||||
&StarsTopDonor::peer);
|
||||
&StarsDonor::peer);
|
||||
if (i != end(_paid.top.topDonors)) {
|
||||
i->stars += stars;
|
||||
} else {
|
||||
@@ -741,8 +724,8 @@ void Messages::addStars(not_null<PeerData*> from, int stars, bool mine) {
|
||||
ranges::stable_sort(
|
||||
_paid.top.topDonors,
|
||||
ranges::greater(),
|
||||
&StarsTopDonor::stars);
|
||||
_paidChanges.fire({});
|
||||
&StarsDonor::stars);
|
||||
_paidChanges.fire({ .peer = from, .stars = stars });
|
||||
}
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -54,18 +54,18 @@ struct MessageDeleteRequest {
|
||||
bool reportSpam = false;
|
||||
};
|
||||
|
||||
struct StarsTopDonor {
|
||||
struct StarsDonor {
|
||||
PeerData *peer = nullptr;
|
||||
int stars = 0;
|
||||
bool my = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarsTopDonor &,
|
||||
const StarsTopDonor &) = default;
|
||||
const StarsDonor &,
|
||||
const StarsDonor &) = default;
|
||||
};
|
||||
|
||||
struct StarsTop {
|
||||
std::vector<StarsTopDonor> topDonors;
|
||||
std::vector<StarsDonor> topDonors;
|
||||
int total = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
|
||||
[[nodiscard]] int reactionsPaidScheduled() const;
|
||||
[[nodiscard]] PeerId reactionsLocalShownPeer() const;
|
||||
void reactionsPaidAdd(int count, std::optional<PeerId> shownPeer = {});
|
||||
void reactionsPaidAdd(int count);
|
||||
void reactionsPaidScheduledCancel();
|
||||
void reactionsPaidSend();
|
||||
void undoScheduledPaidOnDestroy();
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
int my = 0;
|
||||
};
|
||||
[[nodiscard]] PaidLocalState starsLocalState() const;
|
||||
[[nodiscard]] rpl::producer<> starsValueChanges() const {
|
||||
[[nodiscard]] rpl::producer<StarsDonor> starsValueChanges() const {
|
||||
return _paidChanges.events();
|
||||
}
|
||||
[[nodiscard]] const StarsTop &starsTop() const {
|
||||
@@ -155,7 +155,9 @@ private:
|
||||
const TextWithEntities &text,
|
||||
int stars) const;
|
||||
[[nodiscard]] Data::PaidReactionSend startPaidReactionSending();
|
||||
void finishPaidSending(Data::PaidReactionSend send, bool success);
|
||||
void finishPaidSending(
|
||||
Data::PaidReactionSend send,
|
||||
bool success);
|
||||
void addStars(not_null<PeerData*> from, int stars, bool mine);
|
||||
void requestStarsStats();
|
||||
|
||||
@@ -181,7 +183,7 @@ private:
|
||||
|
||||
mtpRequestId _starsTopRequestId = 0;
|
||||
Paid _paid;
|
||||
rpl::event_stream<> _paidChanges;
|
||||
rpl::event_stream<StarsDonor> _paidChanges;
|
||||
bool _paidSendingPending = false;
|
||||
|
||||
TimeId _ttl = 0;
|
||||
|
||||
@@ -73,6 +73,13 @@ constexpr auto kAdminBadgeTextOpacity = 0.6;
|
||||
return minHeight / 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] int CountPriceRadius() {
|
||||
const auto height = st::groupCallPricePadding.top()
|
||||
+ st::normalFont->height
|
||||
+ st::groupCallPricePadding.bottom();
|
||||
return height / 2;
|
||||
}
|
||||
|
||||
[[nodiscard]] int CountPinnedRadius() {
|
||||
const auto height = st::groupCallUserpicPadding.top()
|
||||
+ st::groupCallPinnedUserpic
|
||||
@@ -304,6 +311,7 @@ struct MessagesUi::MessageView {
|
||||
bool removed = false;
|
||||
bool sending = false;
|
||||
bool failed = false;
|
||||
bool simple = false;
|
||||
bool admin = false;
|
||||
bool mine = false;
|
||||
};
|
||||
@@ -333,6 +341,7 @@ MessagesUi::PayedBg::PayedBg(const Ui::StarsColoring &coloring)
|
||||
, pinnedLight(CountPinnedRadius(), light.color())
|
||||
, pinnedDark(CountPinnedRadius(), dark.color())
|
||||
, messageLight(CountMessageRadius(), light.color())
|
||||
, priceDark(CountPriceRadius(), dark.color())
|
||||
, badgeDark(st::roundRadiusLarge, dark.color()) {
|
||||
}
|
||||
|
||||
@@ -518,6 +527,7 @@ void MessagesUi::animateMessageSent(MessageView &entry) {
|
||||
|
||||
void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
const auto &padding = st::groupCallMessagePadding;
|
||||
const auto &pricePadding = st::groupCallPricePadding;
|
||||
|
||||
const auto hasUserpic = !entry.failed;
|
||||
const auto userpicPadding = st::groupCallUserpicPadding;
|
||||
@@ -532,6 +542,9 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
entry.text,
|
||||
std::min(st::groupCallWidth / 2, inner),
|
||||
inner);
|
||||
const auto price = entry.simple
|
||||
? (pricePadding.left() + pricePadding.right() + entry.price.maxWidth())
|
||||
: 0;
|
||||
const auto space = st::normalFont->spacew;
|
||||
const auto nameWidth = entry.name.isEmpty() ? 0 : entry.name.maxWidth();
|
||||
const auto nameLineWidth = nameWidth
|
||||
@@ -546,8 +559,8 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
|
||||
? 0
|
||||
: st::messageTextStyle.font->height;
|
||||
const auto textHeight = size.height();
|
||||
entry.width = std::max(size.width(), std::min(nameLineWidth, inner))
|
||||
+ widthSkip;
|
||||
entry.width = widthSkip
|
||||
+ std::max(size.width() + price, std::min(nameLineWidth, inner));
|
||||
entry.left = _streamMode ? 0 : (_width - entry.width) / 2;
|
||||
entry.textLeft = leftSkip;
|
||||
entry.textTop = padding.top() + nameHeight;
|
||||
@@ -637,6 +650,8 @@ void MessagesUi::setContentFailed(MessageView &entry) {
|
||||
}
|
||||
|
||||
void MessagesUi::setContent(MessageView &entry) {
|
||||
entry.simple = !entry.admin && entry.original.empty() && entry.stars > 0;
|
||||
|
||||
const auto name = nameText(entry.from, entry.place);
|
||||
entry.name = entry.admin
|
||||
? Ui::Text::String(
|
||||
@@ -648,7 +663,7 @@ void MessagesUi::setContent(MessageView &entry) {
|
||||
: Ui::Text::String();
|
||||
if (const auto stars = entry.stars) {
|
||||
entry.price = Ui::Text::String(
|
||||
st::whoReadDateStyle,
|
||||
entry.simple ? st::messageTextStyle : st::whoReadDateStyle,
|
||||
Ui::Text::IconEmoji(
|
||||
&st::starIconEmojiSmall
|
||||
).append(Lang::FormatCountDecimal(stars)),
|
||||
@@ -668,12 +683,14 @@ void MessagesUi::setContent(MessageView &entry) {
|
||||
kMarkupTextOptions,
|
||||
st::groupCallWidth / 8,
|
||||
_crownHelper.context([this, id = entry.id] { repaintMessage(id); }));
|
||||
if (!entry.price.isEmpty()) {
|
||||
if (!entry.simple && !entry.price.isEmpty()) {
|
||||
entry.text.updateSkipBlock(
|
||||
entry.price.maxWidth(),
|
||||
st::normalFont->height);
|
||||
}
|
||||
entry.text.setLink(1, entry.fromLink);
|
||||
if (!entry.simple && !entry.admin) {
|
||||
entry.text.setLink(1, entry.fromLink);
|
||||
}
|
||||
if (entry.text.hasSpoilers()) {
|
||||
const auto id = entry.id;
|
||||
const auto guard = base::make_weak(_messages);
|
||||
@@ -1207,18 +1224,19 @@ void MessagesUi::setupMessagesWidget() {
|
||||
p.setOpacity(scale);
|
||||
p.translate(-mx, -my);
|
||||
}
|
||||
auto bg = (std::unique_ptr<PayedBg>*)nullptr;
|
||||
if (!_streamMode) {
|
||||
_messageBgRect.paint(p, { x, y, width, use });
|
||||
} else if (entry.stars) {
|
||||
const auto coloring = Ui::StarsColoringForCount(
|
||||
colorings,
|
||||
entry.stars);
|
||||
auto &bg = _bgs[ColoringKey(coloring)];
|
||||
if (!bg) {
|
||||
bg = std::make_unique<PayedBg>(coloring);
|
||||
bg = &_bgs[ColoringKey(coloring)];
|
||||
if (!*bg) {
|
||||
*bg = std::make_unique<PayedBg>(coloring);
|
||||
}
|
||||
p.setOpacity(kColoredMessageBgOpacity);
|
||||
bg->messageLight.paint(p, { x, y, width, use });
|
||||
(*bg)->messageLight.paint(p, { x, y, width, use });
|
||||
p.setOpacity(1.);
|
||||
if (_highlightAnimation.animating()
|
||||
&& entry.id == _highlightId) {
|
||||
@@ -1236,7 +1254,6 @@ void MessagesUi::setupMessagesWidget() {
|
||||
}
|
||||
|
||||
const auto textLeft = entry.textLeft;
|
||||
const auto priceSkip = padding.right() / 2;
|
||||
const auto hasUserpic = !entry.failed;
|
||||
if (hasUserpic) {
|
||||
const auto userpicSize = st::groupCallUserpic;
|
||||
@@ -1299,23 +1316,49 @@ void MessagesUi::setupMessagesWidget() {
|
||||
});
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
const auto pricePadding = st::groupCallPricePadding;
|
||||
const auto textRight = padding.right()
|
||||
+ (entry.simple
|
||||
? (entry.price.maxWidth()
|
||||
+ pricePadding.left()
|
||||
+ pricePadding.right())
|
||||
: 0);
|
||||
entry.text.draw(p, {
|
||||
.position = {
|
||||
x + textLeft,
|
||||
y + entry.textTop,
|
||||
},
|
||||
.availableWidth = entry.width - textLeft - padding.right(),
|
||||
.availableWidth = entry.width - textLeft - textRight,
|
||||
.palette = &st::groupCallMessagePalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = now,
|
||||
.paused = !_messages->window()->isActiveWindow(),
|
||||
});
|
||||
if (!entry.price.isEmpty()) {
|
||||
const auto priceRight = x
|
||||
+ entry.width
|
||||
- entry.price.maxWidth();
|
||||
const auto priceLeft = entry.simple
|
||||
? (priceRight
|
||||
- (padding.top() - pricePadding.top())
|
||||
- pricePadding.right())
|
||||
: (priceRight - (padding.right() / 2));
|
||||
const auto priceTop = entry.simple
|
||||
? (y + entry.textTop)
|
||||
: (y + use - st::normalFont->height);
|
||||
if (entry.simple && bg) {
|
||||
p.setOpacity(kDarkOverOpacity);
|
||||
const auto r = QRect(
|
||||
priceLeft,
|
||||
priceTop,
|
||||
entry.price.maxWidth(),
|
||||
st::normalFont->height
|
||||
).marginsAdded(pricePadding);
|
||||
(*bg)->priceDark.paint(p, r);
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
entry.price.draw(p, {
|
||||
.position = {
|
||||
x + entry.width - entry.price.maxWidth() - priceSkip,
|
||||
y + use - st::normalFont->height,
|
||||
},
|
||||
.position = { priceLeft, priceTop },
|
||||
.availableWidth = entry.price.maxWidth(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ private:
|
||||
Ui::RoundRect pinnedLight;
|
||||
Ui::RoundRect pinnedDark;
|
||||
Ui::RoundRect messageLight;
|
||||
Ui::RoundRect priceDark;
|
||||
Ui::RoundRect badgeDark;
|
||||
};
|
||||
|
||||
|
||||
@@ -2109,12 +2109,27 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
const auto over = std::make_shared<bool>();
|
||||
widget->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Enter) {
|
||||
trackControlOver(widget, true);
|
||||
} else if (e->type() == QEvent::Leave) {
|
||||
trackControlOver(widget, false);
|
||||
const auto type = e->type();
|
||||
if (type == QEvent::Enter) {
|
||||
// Enter events may come from widget destructors,
|
||||
// in that case sync-showing tooltip (calling Grab)
|
||||
// crashes the whole thing.
|
||||
*over = true;
|
||||
crl::on_main(widget, [=] {
|
||||
if (*over) {
|
||||
trackControlOver(widget, true);
|
||||
}
|
||||
});
|
||||
} else if (type == QEvent::Leave) {
|
||||
*over = false;
|
||||
crl::on_main(widget, [=] {
|
||||
if (!*over) {
|
||||
trackControlOver(widget, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
@@ -43,18 +43,21 @@ void VideoStreamStarsBox(
|
||||
VideoStreamStarsBoxArgs &&args) {
|
||||
args.show->session().credits().load();
|
||||
|
||||
const auto admin = args.admin;
|
||||
const auto sending = args.sending;
|
||||
auto submitText = [=](rpl::producer<int> amount) {
|
||||
auto nice = std::move(amount) | rpl::map([=](int count) {
|
||||
return Ui::CreditsEmojiSmall().append(
|
||||
Lang::FormatCountDecimal(count));
|
||||
});
|
||||
return (sending
|
||||
? tr::lng_paid_reaction_button
|
||||
: tr::lng_paid_comment_button)(
|
||||
lt_stars,
|
||||
std::move(nice),
|
||||
Ui::Text::RichLangValue);
|
||||
return admin
|
||||
? tr::lng_box_ok(tr::marked)
|
||||
: (sending
|
||||
? tr::lng_paid_reaction_button
|
||||
: tr::lng_paid_comment_button)(
|
||||
lt_stars,
|
||||
std::move(nice),
|
||||
tr::rich);
|
||||
};
|
||||
const auto &show = args.show;
|
||||
const auto session = &show->session();
|
||||
@@ -121,14 +124,17 @@ void VideoStreamStarsBox(
|
||||
.submit = std::move(submitText),
|
||||
.colorings = show->session().appConfig().groupCallColorings(),
|
||||
.balanceValue = session->credits().balanceValue(),
|
||||
.send = [weak, save = args.save](int count, uint64 barePeerId) {
|
||||
save(count);
|
||||
.send = [=, save = args.save](int count, uint64 barePeerId) {
|
||||
if (!admin) {
|
||||
save(count);
|
||||
}
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
},
|
||||
.videoStreamChoosing = !sending,
|
||||
.videoStreamSending = sending,
|
||||
.videoStreamAdmin = admin,
|
||||
.dark = true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ struct VideoStreamStarsBoxArgs {
|
||||
int min = 0;
|
||||
int current = 0;
|
||||
bool sending = false;
|
||||
bool admin = false;
|
||||
Fn<void(int)> save;
|
||||
QString name;
|
||||
};
|
||||
|
||||
@@ -65,14 +65,18 @@ Viewport::Viewport(
|
||||
}
|
||||
|
||||
Viewport::~Viewport() {
|
||||
if (_borrowed && _opengl) {
|
||||
const auto w = static_cast<QOpenGLWidget*>(widget().get());
|
||||
w->makeCurrent();
|
||||
const auto context = w->context();
|
||||
const auto valid = w->isValid()
|
||||
&& context
|
||||
&& (QOpenGLContext::currentContext() == context);
|
||||
ensureBorrowedCleared(valid ? context->functions() : nullptr);
|
||||
if (_borrowed) {
|
||||
if (_opengl) {
|
||||
const auto w = static_cast<QOpenGLWidget*>(widget().get());
|
||||
w->makeCurrent();
|
||||
const auto context = w->context();
|
||||
const auto valid = w->isValid()
|
||||
&& context
|
||||
&& (QOpenGLContext::currentContext() == context);
|
||||
ensureBorrowedCleared(valid ? context->functions() : nullptr);
|
||||
} else {
|
||||
ensureBorrowedCleared();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,13 +791,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||
top += height + st::groupCallVideoSmallSkip;
|
||||
}
|
||||
};
|
||||
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
|
||||
const auto topPeer = _large ? _large->peer().get() : nullptr;
|
||||
const auto reorderNeeded = [&] {
|
||||
if (!topPeer) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &tile : _tiles) {
|
||||
if (tile.get() != _large && tile->row()->peer() == topPeer) {
|
||||
if (tile.get() != _large && tile->peer() == topPeer) {
|
||||
return (tile.get() != _tiles.front().get())
|
||||
&& !tile->trackOrUserpicSize().isEmpty();
|
||||
}
|
||||
@@ -809,7 +813,7 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
|
||||
ranges::stable_partition(
|
||||
_tilesForOrder,
|
||||
[&](not_null<VideoTile*> tile) {
|
||||
return (tile->row()->peer() == topPeer);
|
||||
return (tile->peer() == topPeer);
|
||||
});
|
||||
for (const auto &tile : _tilesForOrder) {
|
||||
layoutNext(tile);
|
||||
@@ -925,6 +929,7 @@ rpl::producer<bool> Viewport::mouseInsideValue() const {
|
||||
|
||||
void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(_opengl);
|
||||
|
||||
if (_borrowedRenderer) {
|
||||
return;
|
||||
@@ -935,6 +940,7 @@ void Viewport::ensureBorrowedRenderer(QOpenGLFunctions &f) {
|
||||
|
||||
void Viewport::ensureBorrowedCleared(QOpenGLFunctions *f) {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(_opengl);
|
||||
|
||||
if (const auto renderer = base::take(_borrowedRenderer)) {
|
||||
renderer->deinit(f);
|
||||
@@ -948,6 +954,23 @@ void Viewport::borrowedPaint(QOpenGLFunctions &f) {
|
||||
_borrowedRenderer->paint(static_cast<QOpenGLWidget*>(widget().get()), f);
|
||||
}
|
||||
|
||||
void Viewport::ensureBorrowedRenderer() {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
if (_borrowedRenderer) {
|
||||
return;
|
||||
}
|
||||
_borrowedRenderer = makeRenderer();
|
||||
}
|
||||
|
||||
void Viewport::ensureBorrowedCleared() {
|
||||
Expects(_borrowed != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
base::take(_borrowedRenderer);
|
||||
}
|
||||
|
||||
void Viewport::borrowedPaint(Painter &p, const QRegion &clip) {
|
||||
Expects(_borrowedRenderer != nullptr);
|
||||
Expects(!_opengl);
|
||||
|
||||
@@ -103,7 +103,11 @@ public:
|
||||
void ensureBorrowedRenderer(QOpenGLFunctions &f);
|
||||
void ensureBorrowedCleared(QOpenGLFunctions *f);
|
||||
void borrowedPaint(QOpenGLFunctions &f);
|
||||
|
||||
void ensureBorrowedRenderer();
|
||||
void ensureBorrowedCleared();
|
||||
void borrowedPaint(Painter &p, const QRegion &clip);
|
||||
|
||||
[[nodiscard]] QPoint borrowedOrigin() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
@@ -460,7 +460,7 @@ void Viewport::RendererGL::validateUserpicFrame(
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
tileData.userpicFrame = PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0);
|
||||
@@ -1239,7 +1239,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
j->stale = false;
|
||||
const auto index = (j - begin(_tileData));
|
||||
_tileDataIndices[i] = index;
|
||||
const auto peer = tiles[i]->row()->peer();
|
||||
const auto peer = tiles[i]->peer();
|
||||
if ((j->peer != peer)
|
||||
|| (j->nameVersion != peer->nameVersion())
|
||||
|| (j->nameRect.width() != width)) {
|
||||
@@ -1263,7 +1263,7 @@ void Viewport::RendererGL::validateDatas() {
|
||||
continue;
|
||||
}
|
||||
const auto id = quintptr(tiles[i]->track().get());
|
||||
const auto peer = tiles[i]->row()->peer();
|
||||
const auto peer = tiles[i]->peer();
|
||||
const auto paused = (tiles[i]->track()->state()
|
||||
== Webrtc::VideoState::Paused);
|
||||
auto index = int(_tileData.size());
|
||||
|
||||
@@ -52,12 +52,14 @@ void Viewport::RendererSW::paintFallback(
|
||||
}
|
||||
paintTile(p, tile.get(), bounding, bg);
|
||||
}
|
||||
const auto fullscreen = _owner->_fullscreen;
|
||||
const auto color = fullscreen
|
||||
? QColor(0, 0, 0)
|
||||
: st::groupCallBg->c;
|
||||
for (const auto &rect : bg) {
|
||||
p.fillRect(rect, color);
|
||||
if (_owner->borrowedOrigin().isNull()) {
|
||||
const auto fullscreen = _owner->_fullscreen;
|
||||
const auto color = fullscreen
|
||||
? QColor(0, 0, 0)
|
||||
: st::groupCallBg->c;
|
||||
for (const auto &rect : bg) {
|
||||
p.fillRect(rect, color);
|
||||
}
|
||||
}
|
||||
for (auto i = _tileData.begin(); i != _tileData.end();) {
|
||||
if (i->second.stale) {
|
||||
@@ -80,7 +82,7 @@ void Viewport::RendererSW::validateUserpicFrame(
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
data.userpicFrame = Images::BlurLargeImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0),
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/group/calls_group_viewport_tile.h"
|
||||
|
||||
#include "calls/group/calls_group_members_row.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/round_rect.h"
|
||||
@@ -33,11 +34,11 @@ Viewport::VideoTile::VideoTile(
|
||||
: _endpoint(endpoint)
|
||||
, _update(std::move(update))
|
||||
, _track(std::move(track))
|
||||
, _peer(_track.row->peer())
|
||||
, _trackSize(std::move(trackSize))
|
||||
, _rtmp(endpoint.rtmp())
|
||||
, _self(self) {
|
||||
Expects(_track.track != nullptr);
|
||||
Expects(_track.row != nullptr);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
_track.track->stateValue(
|
||||
|
||||
@@ -37,6 +37,9 @@ public:
|
||||
[[nodiscard]] not_null<MembersRow*> row() const {
|
||||
return _track.row;
|
||||
}
|
||||
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||
return _peer;
|
||||
}
|
||||
[[nodiscard]] bool rtmp() const {
|
||||
return _rtmp;
|
||||
}
|
||||
@@ -113,8 +116,9 @@ private:
|
||||
|
||||
const VideoEndpoint _endpoint;
|
||||
const Fn<void()> _update;
|
||||
const VideoTileTrack _track;
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
VideoTileTrack _track;
|
||||
QRect _geometry;
|
||||
TileAnimation _animation;
|
||||
rpl::variable<QSize> _trackSize;
|
||||
|
||||
@@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
|
||||
|
||||
const auto kSets = {
|
||||
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
|
||||
Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
|
||||
Set{ { 1, 2774, 8'455'034, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2775, 5'713'503, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2776, 7'347'332, "JoyPixels" }, PreviewPath(3) },
|
||||
};
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
|
||||
@@ -1742,7 +1742,7 @@ void StickersListWidget::showStickerSetBox(
|
||||
}
|
||||
lifetime->destroy();
|
||||
}, *lifetime);
|
||||
document->owner().session().api().requestSpecialStickersForce(
|
||||
document->session().api().requestSpecialStickersForce(
|
||||
setId == Data::Stickers::FavedSetId,
|
||||
setId == Data::Stickers::RecentSetId,
|
||||
false);
|
||||
|
||||
@@ -489,7 +489,12 @@ TabbedSelector::TabbedSelector(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(indexByType(SelectorTab::Stickers));
|
||||
stickers()->showStickerSet(setId);
|
||||
_showRequests.fire({});
|
||||
if (_currentPeer
|
||||
&& Data::CanSend(
|
||||
_currentPeer,
|
||||
ChatRestriction::SendStickers)) {
|
||||
_showRequests.fire({});
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
rpl::merge(
|
||||
@@ -517,7 +522,9 @@ TabbedSelector::TabbedSelector(
|
||||
) | rpl::start_with_next([=](uint64 setId) {
|
||||
_tabsSlider->setActiveSection(indexByType(SelectorTab::Emoji));
|
||||
emoji()->showSet(setId);
|
||||
_showRequests.fire({});
|
||||
if (_currentPeer && Data::CanSendTexts(_currentPeer)) {
|
||||
_showRequests.fire({});
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
if (hasEmojiTab()) {
|
||||
|
||||
@@ -181,3 +181,5 @@ private:
|
||||
[[nodiscard]] CreditsAmount CreditsAmountFromTL(
|
||||
const MTPStarsAmount *amount);
|
||||
[[nodiscard]] MTPStarsAmount StarsAmountToTL(CreditsAmount amount);
|
||||
|
||||
[[nodiscard]] QString PrepareCreditsAmountText(CreditsAmount amount);
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 6003002;
|
||||
constexpr auto AppVersionStr = "6.3.2";
|
||||
constexpr auto AppVersion = 6003006;
|
||||
constexpr auto AppVersionStr = "6.3.6";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -256,3 +257,15 @@ MTPStarsAmount StarsAmountToTL(CreditsAmount amount) {
|
||||
MTP_long(amount.whole() * uint64(1'000'000'000) + amount.nano())
|
||||
) : MTP_starsAmount(MTP_long(amount.whole()), MTP_int(amount.nano()));
|
||||
}
|
||||
|
||||
QString PrepareCreditsAmountText(CreditsAmount amount) {
|
||||
return amount.stars()
|
||||
? tr::lng_action_gift_for_stars(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
amount.value())
|
||||
: tr::lng_action_gift_for_ton(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
amount.value());
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/components/gift_auctions.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -18,6 +19,16 @@ namespace Data {
|
||||
GiftAuctions::GiftAuctions(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _timer([=] { checkSubscriptions(); }) {
|
||||
crl::on_main(_session, [=] {
|
||||
rpl::merge(
|
||||
_session->data().chatsListChanges(),
|
||||
_session->data().chatsListLoadedEvents()
|
||||
) | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
requestActive();
|
||||
}, _lifetime);
|
||||
});
|
||||
}
|
||||
|
||||
GiftAuctions::~GiftAuctions() = default;
|
||||
@@ -50,15 +61,27 @@ rpl::producer<GiftAuctionState> GiftAuctions::state(const QString &slug) {
|
||||
|
||||
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) {
|
||||
if (const auto entry = find(data.vgift_id().v)) {
|
||||
const auto was = myStateKey(entry->state);
|
||||
apply(entry, data.vstate());
|
||||
entry->changes.fire({});
|
||||
if (was != myStateKey(entry->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
} else {
|
||||
requestActive();
|
||||
}
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) {
|
||||
if (const auto entry = find(data.vgift_id().v)) {
|
||||
const auto was = myStateKey(entry->state);
|
||||
apply(entry, data.vuser_state());
|
||||
entry->changes.fire({});
|
||||
if (was != myStateKey(entry->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
} else {
|
||||
requestActive();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +112,7 @@ void GiftAuctions::requestAcquired(
|
||||
.date = data.vdate().v,
|
||||
.bidAmount = int64(data.vbid_amount().v),
|
||||
.round = data.vround().v,
|
||||
.number = data.vgift_num().value_or_empty(),
|
||||
.position = data.vpos().v,
|
||||
.nameHidden = data.is_name_hidden(),
|
||||
});
|
||||
@@ -106,6 +130,80 @@ void GiftAuctions::requestAcquired(
|
||||
}).send();
|
||||
}
|
||||
|
||||
std::optional<Data::UniqueGiftAttributes> GiftAuctions::attributes(
|
||||
uint64 giftId) const {
|
||||
const auto i = _attributes.find(giftId);
|
||||
return (i != end(_attributes) && i->second.waiters.empty())
|
||||
? i->second.lists
|
||||
: std::optional<Data::UniqueGiftAttributes>();
|
||||
}
|
||||
|
||||
void GiftAuctions::requestAttributes(uint64 giftId, Fn<void()> ready) {
|
||||
auto &entry = _attributes[giftId];
|
||||
entry.waiters.push_back(std::move(ready));
|
||||
if (entry.waiters.size() > 1) {
|
||||
return;
|
||||
}
|
||||
_session->api().request(MTPpayments_GetStarGiftUpgradeAttributes(
|
||||
MTP_long(giftId)
|
||||
)).done([=](const MTPpayments_StarGiftUpgradeAttributes &result) {
|
||||
const auto &attributes = result.data().vattributes().v;
|
||||
auto &entry = _attributes[giftId];
|
||||
auto &info = entry.lists;
|
||||
info.models.reserve(attributes.size());
|
||||
info.patterns.reserve(attributes.size());
|
||||
info.backdrops.reserve(attributes.size());
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
info.models.push_back(Api::FromTL(_session, data));
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
info.patterns.push_back(Api::FromTL(_session, data));
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
info.backdrops.push_back(Api::FromTL(data));
|
||||
}, [](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
});
|
||||
}
|
||||
for (const auto &ready : base::take(entry.waiters)) {
|
||||
ready();
|
||||
}
|
||||
}).fail([=] {
|
||||
for (const auto &ready : base::take(_attributes[giftId].waiters)) {
|
||||
ready();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<ActiveAuctions> GiftAuctions::active() const {
|
||||
return _activeChanged.events_starting_with_copy(
|
||||
rpl::empty
|
||||
) | rpl::map([=] {
|
||||
return collectActive();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<bool> GiftAuctions::hasActiveChanges() const {
|
||||
const auto has = hasActive();
|
||||
return _activeChanged.events(
|
||||
) | rpl::map([=] {
|
||||
return hasActive();
|
||||
}) | rpl::combine_previous(
|
||||
has
|
||||
) | rpl::filter([=](bool previous, bool current) {
|
||||
return previous != current;
|
||||
}) | rpl::map([=](bool previous, bool current) {
|
||||
return current;
|
||||
});
|
||||
}
|
||||
|
||||
bool GiftAuctions::hasActive() const {
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
if (myStateKey(entry->state)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GiftAuctions::checkSubscriptions() {
|
||||
const auto now = crl::now();
|
||||
auto next = crl::time();
|
||||
@@ -128,6 +226,102 @@ void GiftAuctions::checkSubscriptions() {
|
||||
}
|
||||
}
|
||||
|
||||
auto GiftAuctions::myStateKey(const GiftAuctionState &state) const
|
||||
-> MyStateKey {
|
||||
if (!state.my.bid) {
|
||||
return {};
|
||||
}
|
||||
auto min = 0;
|
||||
for (const auto &level : state.bidLevels) {
|
||||
if (level.position > state.gift->auctionGiftsPerRound) {
|
||||
break;
|
||||
} else if (!min || min > level.amount) {
|
||||
min = level.amount;
|
||||
}
|
||||
}
|
||||
return {
|
||||
.bid = int(state.my.bid),
|
||||
.position = MyAuctionPosition(state),
|
||||
.version = state.version,
|
||||
};
|
||||
}
|
||||
|
||||
ActiveAuctions GiftAuctions::collectActive() const {
|
||||
auto result = ActiveAuctions();
|
||||
result.list.reserve(_map.size());
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
const auto raw = &entry->state;
|
||||
if (raw->gift && raw->my.date) {
|
||||
result.list.push_back(raw);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64 GiftAuctions::countActiveHash() const {
|
||||
auto result = Api::HashInit();
|
||||
for (const auto &active : collectActive().list) {
|
||||
Api::HashUpdate(result, active->version);
|
||||
Api::HashUpdate(result, active->my.date);
|
||||
}
|
||||
return Api::HashFinalize(result);
|
||||
}
|
||||
|
||||
void GiftAuctions::requestActive() {
|
||||
if (_activeRequestId) {
|
||||
return;
|
||||
}
|
||||
_activeRequestId = _session->api().request(
|
||||
MTPpayments_GetStarGiftActiveAuctions(MTP_long(countActiveHash()))
|
||||
).done([=](const MTPpayments_StarGiftActiveAuctions &result) {
|
||||
result.match([=](const MTPDpayments_starGiftActiveAuctions &data) {
|
||||
const auto owner = &_session->data();
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
|
||||
auto giftsFound = base::flat_set<QString>();
|
||||
const auto &list = data.vauctions().v;
|
||||
giftsFound.reserve(list.size());
|
||||
for (const auto &auction : list) {
|
||||
const auto &data = auction.data();
|
||||
auto gift = Api::FromTL(_session, data.vgift());
|
||||
const auto slug = gift ? gift->auctionSlug : QString();
|
||||
if (slug.isEmpty()) {
|
||||
LOG(("Api Error: Bad auction gift."));
|
||||
continue;
|
||||
}
|
||||
auto &entry = _map[slug];
|
||||
if (!entry) {
|
||||
entry = std::make_unique<Entry>();
|
||||
}
|
||||
const auto raw = entry.get();
|
||||
if (!raw->state.gift) {
|
||||
raw->state.gift = std::move(gift);
|
||||
}
|
||||
apply(raw, data.vstate());
|
||||
apply(raw, data.vuser_state());
|
||||
giftsFound.emplace(slug);
|
||||
}
|
||||
for (const auto &[slug, entry] : _map) {
|
||||
const auto my = &entry->state.my;
|
||||
if (my->date && !giftsFound.contains(slug)) {
|
||||
my->to = nullptr;
|
||||
my->minBidAmount = 0;
|
||||
my->bid = 0;
|
||||
my->date = 0;
|
||||
my->returned = false;
|
||||
giftsFound.emplace(slug);
|
||||
}
|
||||
}
|
||||
for (const auto &slug : giftsFound) {
|
||||
_map[slug]->changes.fire({});
|
||||
}
|
||||
_activeChanged.fire({});
|
||||
}, [](const MTPDpayments_starGiftActiveAuctionsNotModified &) {
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GiftAuctions::request(const QString &slug) {
|
||||
auto &entry = _map[slug];
|
||||
Assert(entry != nullptr);
|
||||
@@ -144,6 +338,9 @@ void GiftAuctions::request(const QString &slug) {
|
||||
raw->requested = false;
|
||||
const auto &data = result.data();
|
||||
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
|
||||
raw->state.gift = Api::FromTL(_session, data.vgift());
|
||||
if (!raw->state.gift) {
|
||||
return;
|
||||
@@ -152,8 +349,7 @@ void GiftAuctions::request(const QString &slug) {
|
||||
const auto ms = timeout * crl::time(1000);
|
||||
raw->state.subscribedTill = ms ? (crl::now() + ms) : -1;
|
||||
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
const auto was = myStateKey(raw->state);
|
||||
apply(raw, data.vstate());
|
||||
apply(raw, data.vuser_state());
|
||||
if (raw->changes.has_consumers()) {
|
||||
@@ -162,6 +358,9 @@ void GiftAuctions::request(const QString &slug) {
|
||||
_timer.callOnce(ms);
|
||||
}
|
||||
}
|
||||
if (was != myStateKey(raw->state)) {
|
||||
_activeChanged.fire({});
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -177,49 +376,72 @@ GiftAuctions::Entry *GiftAuctions::find(uint64 giftId) const {
|
||||
void GiftAuctions::apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionState &state) {
|
||||
Expects(entry->state.gift.has_value());
|
||||
apply(&entry->state, state);
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(
|
||||
not_null<GiftAuctionState*> entry,
|
||||
const MTPStarGiftAuctionState &state) {
|
||||
Expects(entry->gift.has_value());
|
||||
|
||||
const auto raw = &entry->state;
|
||||
state.match([&](const MTPDstarGiftAuctionState &data) {
|
||||
const auto version = data.vversion().v;
|
||||
if (raw->version >= version) {
|
||||
if (entry->version >= version) {
|
||||
return;
|
||||
}
|
||||
const auto owner = &_session->data();
|
||||
raw->startDate = data.vstart_date().v;
|
||||
raw->endDate = data.vend_date().v;
|
||||
raw->minBidAmount = data.vmin_bid_amount().v;
|
||||
entry->startDate = data.vstart_date().v;
|
||||
entry->endDate = data.vend_date().v;
|
||||
entry->minBidAmount = data.vmin_bid_amount().v;
|
||||
const auto &levels = data.vbid_levels().v;
|
||||
raw->bidLevels.clear();
|
||||
raw->bidLevels.reserve(levels.size());
|
||||
entry->bidLevels.clear();
|
||||
entry->bidLevels.reserve(levels.size());
|
||||
for (const auto &level : levels) {
|
||||
auto &entry = raw->bidLevels.emplace_back();
|
||||
auto &bid = entry->bidLevels.emplace_back();
|
||||
const auto &data = level.data();
|
||||
entry.amount = data.vamount().v;
|
||||
entry.position = data.vpos().v;
|
||||
entry.date = data.vdate().v;
|
||||
bid.amount = data.vamount().v;
|
||||
bid.position = data.vpos().v;
|
||||
bid.date = data.vdate().v;
|
||||
}
|
||||
const auto &top = data.vtop_bidders().v;
|
||||
raw->topBidders.clear();
|
||||
raw->topBidders.reserve(top.size());
|
||||
entry->topBidders.clear();
|
||||
entry->topBidders.reserve(top.size());
|
||||
for (const auto &user : top) {
|
||||
raw->topBidders.push_back(owner->user(UserId(user.v)));
|
||||
entry->topBidders.push_back(owner->user(UserId(user.v)));
|
||||
}
|
||||
raw->nextRoundAt = data.vnext_round_at().v;
|
||||
raw->giftsLeft = data.vgifts_left().v;
|
||||
raw->currentRound = data.vcurrent_round().v;
|
||||
raw->totalRounds = data.vtotal_rounds().v;
|
||||
raw->averagePrice = 0;
|
||||
entry->nextRoundAt = data.vnext_round_at().v;
|
||||
entry->giftsLeft = data.vgifts_left().v;
|
||||
entry->currentRound = data.vcurrent_round().v;
|
||||
entry->totalRounds = data.vtotal_rounds().v;
|
||||
const auto &rounds = data.vrounds().v;
|
||||
entry->roundParameters.clear();
|
||||
entry->roundParameters.reserve(rounds.size());
|
||||
for (const auto &round : rounds) {
|
||||
round.match([&](const MTPDstarGiftAuctionRound &data) {
|
||||
entry->roundParameters.push_back({
|
||||
.number = data.vnum().v,
|
||||
.duration = data.vduration().v,
|
||||
});
|
||||
}, [&](const MTPDstarGiftAuctionRoundExtendable &data) {
|
||||
entry->roundParameters.push_back({
|
||||
.number = data.vnum().v,
|
||||
.duration = data.vduration().v,
|
||||
.extendTop = data.vextend_top().v,
|
||||
.extendDuration = data.vextend_window().v,
|
||||
});
|
||||
});
|
||||
}
|
||||
entry->averagePrice = 0;
|
||||
}, [&](const MTPDstarGiftAuctionStateFinished &data) {
|
||||
raw->averagePrice = data.vaverage_price().v;
|
||||
raw->startDate = data.vstart_date().v;
|
||||
raw->endDate = data.vend_date().v;
|
||||
raw->minBidAmount = 0;
|
||||
raw->nextRoundAt
|
||||
= raw->currentRound
|
||||
= raw->totalRounds
|
||||
= raw->giftsLeft
|
||||
= raw->version
|
||||
entry->averagePrice = data.vaverage_price().v;
|
||||
entry->startDate = data.vstart_date().v;
|
||||
entry->endDate = data.vend_date().v;
|
||||
entry->minBidAmount = 0;
|
||||
entry->nextRoundAt
|
||||
= entry->currentRound
|
||||
= entry->totalRounds
|
||||
= entry->giftsLeft
|
||||
= entry->version
|
||||
= 0;
|
||||
}, [&](const MTPDstarGiftAuctionStateNotModified &data) {
|
||||
});
|
||||
@@ -228,16 +450,32 @@ void GiftAuctions::apply(
|
||||
void GiftAuctions::apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionUserState &state) {
|
||||
apply(&entry->state.my, state);
|
||||
}
|
||||
|
||||
void GiftAuctions::apply(
|
||||
not_null<StarGiftAuctionMyState*> entry,
|
||||
const MTPStarGiftAuctionUserState &state) {
|
||||
const auto &data = state.data();
|
||||
const auto raw = &entry->state.my;
|
||||
raw->to = data.vbid_peer()
|
||||
entry->to = data.vbid_peer()
|
||||
? _session->data().peer(peerFromMTP(*data.vbid_peer())).get()
|
||||
: nullptr;
|
||||
raw->minBidAmount = data.vmin_bid_amount().value_or(0);
|
||||
raw->bid = data.vbid_amount().value_or(0);
|
||||
raw->date = data.vbid_date().value_or(0);
|
||||
raw->gotCount = data.vacquired_count().v;
|
||||
raw->returned = data.is_returned();
|
||||
entry->minBidAmount = data.vmin_bid_amount().value_or(0);
|
||||
entry->bid = data.vbid_amount().value_or(0);
|
||||
entry->date = data.vbid_date().value_or(0);
|
||||
entry->gotCount = data.vacquired_count().v;
|
||||
entry->returned = data.is_returned();
|
||||
}
|
||||
|
||||
int MyAuctionPosition(const GiftAuctionState &state) {
|
||||
const auto &levels = state.bidLevels;
|
||||
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
|
||||
if (i->amount < state.my.bid
|
||||
|| (i->amount == state.my.bid && i->date >= state.my.date)) {
|
||||
return i->position;
|
||||
}
|
||||
}
|
||||
return (levels.empty() ? 0 : levels.back().position) + 1;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -31,11 +31,19 @@ struct StarGiftAuctionMyState {
|
||||
bool returned = false;
|
||||
};
|
||||
|
||||
struct GiftAuctionRound {
|
||||
int number = 0;
|
||||
TimeId duration = 0;
|
||||
int extendTop = 0;
|
||||
TimeId extendDuration = 0;
|
||||
};
|
||||
|
||||
struct GiftAuctionState {
|
||||
std::optional<StarGift> gift;
|
||||
StarGiftAuctionMyState my;
|
||||
std::vector<GiftAuctionBidLevel> bidLevels;
|
||||
std::vector<not_null<UserData*>> topBidders;
|
||||
std::vector<GiftAuctionRound> roundParameters;
|
||||
crl::time subscribedTill = 0;
|
||||
int64 minBidAmount = 0;
|
||||
int64 averagePrice = 0;
|
||||
@@ -58,10 +66,15 @@ struct GiftAcquired {
|
||||
TimeId date = 0;
|
||||
int64 bidAmount = 0;
|
||||
int round = 0;
|
||||
int number = 0;
|
||||
int position = 0;
|
||||
bool nameHidden = false;
|
||||
};
|
||||
|
||||
struct ActiveAuctions {
|
||||
std::vector<not_null<GiftAuctionState*>> list;
|
||||
};
|
||||
|
||||
class GiftAuctions final {
|
||||
public:
|
||||
explicit GiftAuctions(not_null<Main::Session*> session);
|
||||
@@ -73,31 +86,72 @@ public:
|
||||
void apply(const MTPDupdateStarGiftAuctionUserState &data);
|
||||
|
||||
void requestAcquired(
|
||||
uint64 giftId,
|
||||
uint64 giftId,
|
||||
Fn<void(std::vector<Data::GiftAcquired>)> done);
|
||||
|
||||
[[nodiscard]] std::optional<Data::UniqueGiftAttributes> attributes(
|
||||
uint64 giftId) const;
|
||||
void requestAttributes(uint64 giftId, Fn<void()> ready);
|
||||
|
||||
[[nodiscard]] rpl::producer<ActiveAuctions> active() const;
|
||||
[[nodiscard]] rpl::producer<bool> hasActiveChanges() const;
|
||||
[[nodiscard]] bool hasActive() const;
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
GiftAuctionState state;
|
||||
rpl::event_stream<> changes;
|
||||
bool requested = false;
|
||||
};
|
||||
struct MyStateKey {
|
||||
int bid = 0;
|
||||
int position = 0;
|
||||
int version = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return bid != 0;
|
||||
}
|
||||
friend inline bool operator==(MyStateKey, MyStateKey) = default;
|
||||
};
|
||||
struct Attributes {
|
||||
Data::UniqueGiftAttributes lists;
|
||||
std::vector<Fn<void()>> waiters;
|
||||
};
|
||||
|
||||
void request(const QString &slug);
|
||||
Entry *find(uint64 giftId) const;
|
||||
void apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionState &state);
|
||||
void apply(
|
||||
not_null<GiftAuctionState*> entry,
|
||||
const MTPStarGiftAuctionState &state);
|
||||
void apply(
|
||||
not_null<Entry*> entry,
|
||||
const MTPStarGiftAuctionUserState &state);
|
||||
void apply(
|
||||
not_null<StarGiftAuctionMyState*> entry,
|
||||
const MTPStarGiftAuctionUserState &state);
|
||||
void checkSubscriptions();
|
||||
|
||||
[[nodiscard]] MyStateKey myStateKey(const GiftAuctionState &state) const;
|
||||
[[nodiscard]] ActiveAuctions collectActive() const;
|
||||
[[nodiscard]] uint64 countActiveHash() const;
|
||||
void requestActive();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _timer;
|
||||
base::flat_map<QString, std::unique_ptr<Entry>> _map;
|
||||
base::flat_map<uint64, Attributes> _attributes;
|
||||
|
||||
rpl::event_stream<> _activeChanged;
|
||||
mtpRequestId _activeRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] int MyAuctionPosition(const GiftAuctionState &state);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
186
Telegram/SourceFiles/data/components/passkeys.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 "data/components/passkeys.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_passkey_deserialize.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "platform/platform_webauthn.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTimeoutMs = 5000;
|
||||
|
||||
[[nodiscard]] PasskeyEntry FromTL(const MTPDpasskey &data) {
|
||||
return PasskeyEntry{
|
||||
.id = qs(data.vid()),
|
||||
.name = qs(data.vname()),
|
||||
.date = data.vdate().v,
|
||||
.softwareEmojiId = data.vsoftware_emoji_id().value_or(0),
|
||||
.lastUsageDate = data.vlast_usage_date().value_or(0),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Passkeys::Passkeys(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
Passkeys::~Passkeys() = default;
|
||||
|
||||
void Passkeys::initRegistration(
|
||||
Fn<void(const Data::Passkey::RegisterData&)> done) {
|
||||
_session->api().request(MTPaccount_InitPasskeyRegistration(
|
||||
)).done([=](const MTPaccount_PasskeyRegistrationOptions &result) {
|
||||
const auto &data = result.data();
|
||||
const auto jsonData = data.voptions().data().vdata().v;
|
||||
if (const auto p = Data::Passkey::DeserializeRegisterData(jsonData)) {
|
||||
done(*p);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Passkeys::registerPasskey(
|
||||
const Platform::WebAuthn::RegisterResult &result,
|
||||
Fn<void()> done) {
|
||||
const auto credentialIdBase64 = QString::fromUtf8(
|
||||
result.credentialId.toBase64(QByteArray::Base64UrlEncoding));
|
||||
_session->api().request(MTPaccount_RegisterPasskey(
|
||||
MTP_inputPasskeyCredentialPublicKey(
|
||||
MTP_string(credentialIdBase64),
|
||||
MTP_string(credentialIdBase64),
|
||||
MTP_inputPasskeyResponseRegister(
|
||||
MTP_dataJSON(MTP_bytes(result.clientDataJSON)),
|
||||
MTP_bytes(result.attestationObject)))
|
||||
)).done([=](const MTPPasskey &result) {
|
||||
_passkeys.emplace_back(FromTL(result.data()));
|
||||
_listUpdated.fire({});
|
||||
done();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Passkeys::deletePasskey(
|
||||
const QString &id,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
_session->api().request(MTPaccount_DeletePasskey(
|
||||
MTP_string(id)
|
||||
)).done([=] {
|
||||
_lastRequestTime = 0;
|
||||
_listKnown = false;
|
||||
loadList();
|
||||
done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<> Passkeys::requestList() {
|
||||
if (crl::now() - _lastRequestTime > kTimeoutMs) {
|
||||
if (!_listRequestId) {
|
||||
loadList();
|
||||
}
|
||||
return _listUpdated.events();
|
||||
} else {
|
||||
return _listUpdated.events_starting_with(rpl::empty_value());
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<PasskeyEntry> &Passkeys::list() const {
|
||||
return _passkeys;
|
||||
}
|
||||
|
||||
bool Passkeys::listKnown() const {
|
||||
return _listKnown;
|
||||
}
|
||||
|
||||
void Passkeys::loadList() {
|
||||
_lastRequestTime = crl::now();
|
||||
_listRequestId = _session->api().request(MTPaccount_GetPasskeys(
|
||||
)).done([=](const MTPaccount_Passkeys &result) {
|
||||
_listRequestId = 0;
|
||||
_listKnown = true;
|
||||
const auto &data = result.data();
|
||||
_passkeys.clear();
|
||||
_passkeys.reserve(data.vpasskeys().v.size());
|
||||
for (const auto &passkey : data.vpasskeys().v) {
|
||||
_passkeys.emplace_back(FromTL(passkey.data()));
|
||||
}
|
||||
_listUpdated.fire({});
|
||||
}).fail([=] {
|
||||
_listRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool Passkeys::canRegister() const {
|
||||
const auto max = _session->appConfig().passkeysAccountPasskeysMax();
|
||||
return Platform::WebAuthn::IsSupported() && _passkeys.size() < max;
|
||||
}
|
||||
|
||||
bool Passkeys::possible() const {
|
||||
return _session->appConfig().settingsDisplayPasskeys();
|
||||
}
|
||||
|
||||
void InitPasskeyLogin(
|
||||
MTP::Sender &api,
|
||||
Fn<void(const Data::Passkey::LoginData&)> done) {
|
||||
api.request(MTPauth_InitPasskeyLogin(
|
||||
MTP_int(ApiId),
|
||||
MTP_string(ApiHash)
|
||||
)).done([=](const MTPauth_PasskeyLoginOptions &result) {
|
||||
const auto &data = result.data();
|
||||
if (const auto p = Passkey::DeserializeLoginData(
|
||||
data.voptions().data().vdata().v)) {
|
||||
done(*p);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FinishPasskeyLogin(
|
||||
MTP::Sender &api,
|
||||
int initialDc,
|
||||
const Platform::WebAuthn::LoginResult &result,
|
||||
Fn<void(const MTPauth_Authorization&)> done,
|
||||
Fn<void(QString)> fail) {
|
||||
const auto userHandleStr = QString::fromUtf8(result.userHandle);
|
||||
const auto parts = userHandleStr.split(':');
|
||||
if (parts.size() != 2) {
|
||||
return;
|
||||
}
|
||||
const auto userDc = parts[0].toInt();
|
||||
const auto credentialIdBase64 = result.credentialId.toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
const auto credential = MTP_inputPasskeyCredentialPublicKey(
|
||||
MTP_string(credentialIdBase64.toStdString()),
|
||||
MTP_string(credentialIdBase64.toStdString()),
|
||||
MTP_inputPasskeyResponseLogin(
|
||||
MTP_dataJSON(MTP_bytes(result.clientDataJSON)),
|
||||
MTP_bytes(result.authenticatorData),
|
||||
MTP_bytes(result.signature),
|
||||
MTP_string(userHandleStr.toStdString())
|
||||
)
|
||||
);
|
||||
const auto flags = (userDc != initialDc)
|
||||
? MTPauth_finishPasskeyLogin::Flag::f_from_dc_id
|
||||
: MTPauth_finishPasskeyLogin::Flags(0);
|
||||
api.request(MTPauth_FinishPasskeyLogin(
|
||||
MTP_flags(flags),
|
||||
credential,
|
||||
MTP_int(initialDc),
|
||||
MTP_long(0)
|
||||
)).toDC(
|
||||
userDc
|
||||
).done(done).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
79
Telegram/SourceFiles/data/components/passkeys.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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::Passkey {
|
||||
struct RegisterData;
|
||||
struct LoginData;
|
||||
} // namespace Data::Passkey
|
||||
namespace Platform::WebAuthn {
|
||||
struct RegisterResult;
|
||||
struct LoginResult;
|
||||
} // namespace Platform::WebAuthn
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace MTP {
|
||||
class Sender;
|
||||
} // namespace MTP
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct PasskeyEntry {
|
||||
QString id;
|
||||
QString name;
|
||||
TimeId date = 0;
|
||||
DocumentId softwareEmojiId = 0;
|
||||
TimeId lastUsageDate = 0;
|
||||
};
|
||||
|
||||
class Passkeys final {
|
||||
public:
|
||||
explicit Passkeys(not_null<Main::Session*> session);
|
||||
~Passkeys();
|
||||
|
||||
void initRegistration(Fn<void(const Data::Passkey::RegisterData&)> done);
|
||||
void registerPasskey(
|
||||
const Platform::WebAuthn::RegisterResult &result,
|
||||
Fn<void()> done);
|
||||
void deletePasskey(
|
||||
const QString &id,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
[[nodiscard]] rpl::producer<> requestList();
|
||||
[[nodiscard]] const std::vector<PasskeyEntry> &list() const;
|
||||
[[nodiscard]] bool listKnown() const;
|
||||
[[nodiscard]] bool canRegister() const;
|
||||
[[nodiscard]] bool possible() const;
|
||||
|
||||
private:
|
||||
void loadList();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
std::vector<PasskeyEntry> _passkeys;
|
||||
rpl::event_stream<> _listUpdated;
|
||||
crl::time _lastRequestTime = 0;
|
||||
mtpRequestId _listRequestId = 0;
|
||||
bool _listKnown = false;
|
||||
|
||||
};
|
||||
|
||||
void InitPasskeyLogin(
|
||||
MTP::Sender &api,
|
||||
Fn<void(const Data::Passkey::LoginData&)> done);
|
||||
|
||||
void FinishPasskeyLogin(
|
||||
MTP::Sender &api,
|
||||
int initialDc,
|
||||
const Platform::WebAuthn::LoginResult &result,
|
||||
Fn<void(const MTPauth_Authorization&)> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
} // namespace Data
|
||||
@@ -40,6 +40,10 @@ struct CreditsHistoryEntry final {
|
||||
return !id.isEmpty();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isLiveStoryReaction() const {
|
||||
return paidMessagesCount && reaction && !bareMsgId;
|
||||
}
|
||||
|
||||
using PhotoId = uint64;
|
||||
enum class PeerType {
|
||||
Peer,
|
||||
@@ -74,6 +78,7 @@ struct CreditsHistoryEntry final {
|
||||
uint64 giftChannelSavedId = 0;
|
||||
uint64 stargiftId = 0;
|
||||
QString giftPrepayUpgradeHash;
|
||||
QString giftTitle;
|
||||
std::shared_ptr<UniqueGift> uniqueGift;
|
||||
Fn<std::vector<CreditsHistoryEntry>()> pinnedSavedGifts;
|
||||
uint64 nextToUpgradeStickerId = 0;
|
||||
@@ -101,6 +106,7 @@ struct CreditsHistoryEntry final {
|
||||
int starsForDetailsRemove = 0;
|
||||
int premiumMonthsForStars = 0;
|
||||
int floodSkip = 0;
|
||||
int giftNumber = 0;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool stargift : 1 = false;
|
||||
|
||||
@@ -431,7 +431,7 @@ void DocumentMedia::GenerateGoodThumbnail(
|
||||
document->setGoodThumbnailChecked(false);
|
||||
return;
|
||||
}
|
||||
const auto guard = base::make_weak(&document->owner().session());
|
||||
const auto guard = base::make_weak(&document->session());
|
||||
crl::async([=, location = std::move(location)] {
|
||||
const auto filepath = (location && location->accessEnable())
|
||||
? location->name()
|
||||
|
||||
@@ -45,7 +45,7 @@ WebPageDraft WebPageDraft::FromItem(not_null<HistoryItem*> item) {
|
||||
Draft::Draft(
|
||||
const TextWithTags &textWithTags,
|
||||
FullReplyTo reply,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
const MessageCursor &cursor,
|
||||
WebPageDraft webpage,
|
||||
mtpRequestId saveRequestId)
|
||||
@@ -60,7 +60,7 @@ Draft::Draft(
|
||||
Draft::Draft(
|
||||
not_null<const Ui::InputField*> field,
|
||||
FullReplyTo reply,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
WebPageDraft webpage,
|
||||
mtpRequestId saveRequestId)
|
||||
: textWithTags(field->getTextWithTags())
|
||||
@@ -110,7 +110,7 @@ void ApplyPeerCloudDraft(
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
auto suggest = SuggestPostOptions();
|
||||
auto suggest = SuggestOptions();
|
||||
if (!history->suggestDraftAllowed()) {
|
||||
// Don't apply suggest options in unsupported chats.
|
||||
} else if (const auto suggested = draft.vsuggested_post()) {
|
||||
@@ -173,7 +173,7 @@ void SetChatLinkDraft(not_null<PeerData*> peer, TextWithEntities draft) {
|
||||
.topicRootId = topicRootId,
|
||||
.monoforumPeerId = monoforumPeerId,
|
||||
},
|
||||
SuggestPostOptions(),
|
||||
SuggestOptions(),
|
||||
cursor,
|
||||
WebPageDraft()));
|
||||
history->clearLocalEditDraft(topicRootId, monoforumPeerId);
|
||||
|
||||
@@ -52,21 +52,21 @@ struct Draft {
|
||||
Draft(
|
||||
const TextWithTags &textWithTags,
|
||||
FullReplyTo reply,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
const MessageCursor &cursor,
|
||||
WebPageDraft webpage,
|
||||
mtpRequestId saveRequestId = 0);
|
||||
Draft(
|
||||
not_null<const Ui::InputField*> field,
|
||||
FullReplyTo reply,
|
||||
SuggestPostOptions suggest,
|
||||
SuggestOptions suggest,
|
||||
WebPageDraft webpage,
|
||||
mtpRequestId saveRequestId = 0);
|
||||
|
||||
TimeId date = 0;
|
||||
TextWithTags textWithTags;
|
||||
FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft.
|
||||
SuggestPostOptions suggest;
|
||||
SuggestOptions suggest;
|
||||
MessageCursor cursor;
|
||||
WebPageDraft webpage;
|
||||
mtpRequestId saveRequestId = 0;
|
||||
|
||||
@@ -2606,10 +2606,11 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
||||
.service = true,
|
||||
.hideServiceText = true,
|
||||
});
|
||||
} else if (_data.type == GiftType::ChatTheme) {
|
||||
} else if (_data.type == GiftType::ChatTheme
|
||||
|| _data.type == GiftType::GiftOffer) {
|
||||
return std::make_unique<HistoryView::ServiceBox>(
|
||||
message,
|
||||
std::make_unique<HistoryView::GiftThemeBox>(message, this));
|
||||
std::make_unique<HistoryView::GiftServiceBox>(message, this));
|
||||
} else if (const auto &unique = _data.unique) {
|
||||
return std::make_unique<HistoryView::MediaGeneric>(
|
||||
message,
|
||||
|
||||
@@ -140,6 +140,7 @@ enum class GiftType : uchar {
|
||||
StarGift, // count - stars
|
||||
ChatTheme,
|
||||
BirthdaySuggest,
|
||||
GiftOffer,
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
@@ -154,6 +155,7 @@ struct GiftCode {
|
||||
PeerData *channelFrom = nullptr;
|
||||
uint64 channelSavedId = 0;
|
||||
QString giftPrepayUpgradeHash;
|
||||
QString giftTitle;
|
||||
MsgId giveawayMsgId = 0;
|
||||
MsgId realGiftMsgId = 0;
|
||||
int starsConverted = 0;
|
||||
@@ -161,6 +163,7 @@ struct GiftCode {
|
||||
int starsUpgradedBySender = 0;
|
||||
int starsForDetailsRemove = 0;
|
||||
int starsBid = 0;
|
||||
int giftNum = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int64 count = 0;
|
||||
|
||||
@@ -207,12 +207,13 @@ struct FullReplyTo {
|
||||
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
|
||||
};
|
||||
|
||||
struct SuggestPostOptions {
|
||||
struct SuggestOptions {
|
||||
uint32 exists : 1 = 0;
|
||||
uint32 priceWhole : 31 = 0;
|
||||
uint32 priceNano : 31 = 0;
|
||||
uint32 ton : 1 = 0;
|
||||
TimeId date = 0;
|
||||
TimeId offerDuration = 0;
|
||||
|
||||
[[nodiscard]] CreditsAmount price() const {
|
||||
return CreditsAmount(
|
||||
@@ -226,11 +227,11 @@ struct SuggestPostOptions {
|
||||
}
|
||||
|
||||
friend inline auto operator<=>(
|
||||
SuggestPostOptions,
|
||||
SuggestPostOptions) = default;
|
||||
SuggestOptions,
|
||||
SuggestOptions) = default;
|
||||
friend inline bool operator==(
|
||||
SuggestPostOptions,
|
||||
SuggestPostOptions) = default;
|
||||
SuggestOptions,
|
||||
SuggestOptions) = default;
|
||||
};
|
||||
|
||||
struct GlobalMsgId {
|
||||
|
||||
121
Telegram/SourceFiles/data/data_passkey_deserialize.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 "data/data_passkey_deserialize.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace Data::Passkey {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::string SerializeClientData(
|
||||
const QByteArray &challenge,
|
||||
const QString &type) {
|
||||
auto obj = QJsonObject();
|
||||
obj["type"] = type;
|
||||
obj["challenge"] = QString::fromUtf8(
|
||||
challenge.toBase64(QByteArray::Base64UrlEncoding
|
||||
| QByteArray::OmitTrailingEquals));
|
||||
obj["origin"] = "https://telegram.org";
|
||||
obj["crossOrigin"] = false;
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact).toStdString();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<RegisterData> DeserializeRegisterData(
|
||||
const QByteArray &jsonData) {
|
||||
auto doc = QJsonDocument::fromJson(jsonData);
|
||||
if (!doc.isObject()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
auto publicKey = root["publicKey"].toObject();
|
||||
if (publicKey.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto data = RegisterData();
|
||||
|
||||
auto rp = publicKey["rp"].toObject();
|
||||
data.rp.id = rp["id"].toString();
|
||||
data.rp.name = rp["name"].toString();
|
||||
|
||||
auto user = publicKey["user"].toObject();
|
||||
data.user.id = QByteArray::fromBase64(
|
||||
user["id"].toString().toUtf8());
|
||||
data.user.name = user["name"].toString();
|
||||
data.user.displayName = user["displayName"].toString();
|
||||
|
||||
data.challenge = QByteArray::fromBase64(
|
||||
publicKey["challenge"].toString().toUtf8(),
|
||||
QByteArray::Base64UrlEncoding);
|
||||
|
||||
auto params = publicKey["pubKeyCredParams"].toArray();
|
||||
for (const auto ¶m : params) {
|
||||
auto obj = param.toObject();
|
||||
CredentialParameter cp;
|
||||
cp.type = obj["type"].toString();
|
||||
cp.alg = obj["alg"].toInt();
|
||||
data.pubKeyCredParams.push_back(cp);
|
||||
}
|
||||
|
||||
data.timeout = publicKey["timeout"].toInt(60000);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string SerializeClientDataCreate(const QByteArray &challenge) {
|
||||
return SerializeClientData(challenge, "webauthn.create");
|
||||
}
|
||||
|
||||
std::string SerializeClientDataGet(const QByteArray &challenge) {
|
||||
return SerializeClientData(challenge, "webauthn.get");
|
||||
}
|
||||
|
||||
std::optional<LoginData> DeserializeLoginData(
|
||||
const QByteArray &jsonData) {
|
||||
auto doc = QJsonDocument::fromJson(jsonData);
|
||||
if (!doc.isObject()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
auto publicKey = root["publicKey"].toObject();
|
||||
if (publicKey.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto data = LoginData();
|
||||
data.challenge = QByteArray::fromBase64(
|
||||
publicKey["challenge"].toString().toUtf8(),
|
||||
QByteArray::Base64UrlEncoding);
|
||||
data.rpId = publicKey["rpId"].toString();
|
||||
data.timeout = publicKey["timeout"].toInt(60000);
|
||||
data.userVerification = publicKey["userVerification"].toString();
|
||||
|
||||
if (publicKey.contains("allowCredentials")) {
|
||||
auto allowList = publicKey["allowCredentials"].toArray();
|
||||
for (const auto &cred : allowList) {
|
||||
auto credObj = cred.toObject();
|
||||
Credential credential;
|
||||
credential.id = QByteArray::fromBase64(
|
||||
credObj["id"].toString().toUtf8(),
|
||||
QByteArray::Base64UrlEncoding);
|
||||
credential.type = credObj["type"].toString();
|
||||
data.allowCredentials.push_back(credential);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace Data::Passkey
|
||||