Compare commits

..

142 Commits

Author SHA1 Message Date
John Preston
1c4f663941 Version 6.3.6.
- Fix crash in media viewer.
- Fix crash in experimental settings.
2025-12-06 00:07:39 +04:00
John Preston
94f9321db9 Fix crash in experimental settings opening. 2025-12-06 00:05:31 +04:00
John Preston
ae70b10cea Fix crash in media viewer. 2025-12-05 23:58:08 +04:00
John Preston
4f685552e7 Version 6.3.5.
- Offer stars or TON for unique gifts.
- Preview gift auctions before they start.
- Support passkey login on Windows.
2025-12-05 23:11:53 +04:00
John Preston
e085a76165 Fix build with GCC. 2025-12-05 23:11:32 +04:00
23rd
30bd3ed013 Removed WebAuthn support from entitlements. 2025-12-05 23:11:32 +04:00
23rd
25edab4c94 Fixed build with Xcode. 2025-12-05 23:11:32 +04:00
23rd
3aa241d825 Removed WebAuthn support for macOS for now. 2025-12-05 23:11:32 +04:00
John Preston
9b867af7fd Use known gift number in some places. 2025-12-05 20:11:54 +04:00
John Preston
0df3be8630 Use better ratio stars/usd/ton. 2025-12-05 20:11:54 +04:00
John Preston
542326af8f Show offer value diff percent. 2025-12-05 20:11:54 +04:00
John Preston
ea5052e69e Add "Ban Users" channel admin right. 2025-12-05 20:11:54 +04:00
John Preston
2dd96b2269 Better phrases for upcoming auctions. 2025-12-05 20:11:54 +04:00
John Preston
627152e2a9 Nice gifts promo box. 2025-12-05 20:11:54 +04:00
John Preston
f01c93ed58 Update API scheme on layer 220. 2025-12-05 20:11:54 +04:00
John Preston
6fe61ed58a Add gifts premium promo. 2025-12-05 20:11:54 +04:00
John Preston
43347f671c Show rarity in gift variants preview. 2025-12-05 20:11:54 +04:00
John Preston
0b67fa65f2 Full upgradable variants preview. 2025-12-05 20:11:54 +04:00
John Preston
65b3a36984 Implement upcoming auction preview box. 2025-12-05 20:11:54 +04:00
John Preston
b08cf75f0b Start auction preview display. 2025-12-05 20:11:54 +04:00
John Preston
0cc21e5ca2 Update API scheme on layer 220. 2025-12-05 20:11:54 +04:00
John Preston
48f9a92cc3 Improve Checkbox / Button accessibility. 2025-12-05 20:11:54 +04:00
John Preston
939882ef68 Show profile design gift wear promo. 2025-12-05 20:11:54 +04:00
John Preston
52084cf0ae Apply correct min/max offer values. 2025-12-05 20:11:54 +04:00
John Preston
f06f654191 Confirm making an offer. 2025-12-05 20:11:53 +04:00
John Preston
356d20542e Use nice radius in offer with buttons. 2025-12-05 20:11:53 +04:00
John Preston
31ea4cfe80 Process offers with accept / reject. 2025-12-05 20:11:53 +04:00
John Preston
1e89ee4e50 Show Reject/Accept buttons for offers. 2025-12-05 20:11:53 +04:00
John Preston
6fccbf036c Rename HistoryMessage[SuggestedPost->Suggestion]. 2025-12-05 20:11:53 +04:00
John Preston
41d206e354 Rename SuggestPostOptions to SuggestOptions. 2025-12-05 20:11:53 +04:00
John Preston
23880ac6c1 Update API scheme to layer 220.
Allow offering to buy gifts.
2025-12-05 20:11:53 +04:00
John Preston
4439cbf553 Pass effect_id to forward message requests. 2025-12-05 20:11:53 +04:00
23rd
f506f1b830 Added read availability of passkeys from appConfig. 2025-12-05 20:11:53 +04:00
23rd
feb1ea6502 Wrapped text recognition on macOS with experimental toggle. 2025-12-05 20:11:53 +04:00
23rd
fea80b4919 Wrapped passkeys on macOS with experimental toggle due to instability. 2025-12-05 20:11:53 +04:00
23rd
373bb8d74c Improved display of ripple on release in buttons from subsection tabs. 2025-12-05 20:11:53 +04:00
23rd
3ddefd78ba Fixed display of tabbed selector on installed set for read-only peers. 2025-12-05 20:11:53 +04:00
23rd
d62e4da163 Added simple cache of recognized text to media view overlay. 2025-12-05 20:11:53 +04:00
23rd
8c60863e11 Added ability to copy recognized text from media view overlay. 2025-12-05 20:11:53 +04:00
23rd
d5be8c8989 Added initial ability to recognize text to media view overlay. 2025-12-05 20:11:53 +04:00
23rd
255b30e88a Added initial implementation of text recognition for macOS. 2025-12-05 20:11:53 +04:00
23rd
d2dd124be0 Added dummy platform files for text recognition. 2025-12-05 20:11:53 +04:00
23rd
1053b30a6d Added api support for max count of passkeys for accounts. 2025-12-05 18:15:54 +04:00
23rd
e7c1073e13 Added initial error handler to passkeys processing. 2025-12-05 18:15:54 +04:00
23rd
0480c6a4af Added initial ability to login with passkey. 2025-12-05 18:15:54 +04:00
23rd
c70a49c0f3 Added initial api support to login with passkey. 2025-12-05 18:15:54 +04:00
John Preston
7840fd481a Use correct icon for passkey in Settings. 2025-12-05 18:15:54 +04:00
23rd
2a8b491c95 Replaced pure text with Text::String in list of passkeys in settings. 2025-12-05 18:15:54 +04:00
23rd
39c4344047 Added divider with description to section of settings for passkeys. 2025-12-05 18:15:54 +04:00
23rd
cdb58e4ebd Added button for new passkeys to section of settings. 2025-12-05 18:15:54 +04:00
23rd
972325fb6d Added menu to simple list of passkeys in section of settings. 2025-12-05 18:15:54 +04:00
23rd
933b6bedc9 Added icons to simple list of passkeys in section of settings. 2025-12-05 18:15:54 +04:00
23rd
2db8a5d00a Added initial simple list of passkeys to section of settings. 2025-12-05 18:15:54 +04:00
23rd
be043ea349 Added initial api support to delete passkey. 2025-12-05 18:15:54 +04:00
23rd
e531abf31b Added header with lottie to section of settings for passkeys. 2025-12-05 18:15:54 +04:00
23rd
a6af680e59 Added initial entry point to section of settings for passkeys. 2025-12-05 18:15:54 +04:00
23rd
8292334c9b Moved out Settings::Passkeys class from header. 2025-12-05 18:15:54 +04:00
23rd
f20c5a1d3c Added initial box as entry point for passkey creation. 2025-12-05 18:15:53 +04:00
23rd
d1e2ec0309 Added initial api support to request of passkeys for account. 2025-12-05 18:15:53 +04:00
23rd
fe91cae8bc Added initial api support to register passkey. 2025-12-05 18:15:53 +04:00
23rd
7bb30bc4a8 Added initial dummy files for component of passkeys. 2025-12-05 18:15:53 +04:00
23rd
2d41d5903b Added initial dummy files for settings section of passkeys. 2025-12-05 18:15:53 +04:00
23rd
df672ffaf5 Added phrases for settings section of passkeys. 2025-12-05 18:15:53 +04:00
23rd
cb100623fb Added initial macOS API support for passkeys. 2025-12-05 18:15:53 +04:00
23rd
30ef7270b3 Added initial Win API support for passkeys. 2025-12-05 18:15:53 +04:00
23rd
91694a69eb Added initial implementation of deserialization data for passkeys. 2025-12-05 18:15:53 +04:00
23rd
cb07bcf0db Added initial dummy platform files for passkeys. 2025-12-05 18:15:53 +04:00
23rd
a31e384409 Added dummy files for credential data of passkeys. 2025-12-05 18:15:53 +04:00
John Preston
4829c6d028 Update API scheme to layer 219. 2025-12-05 18:15:53 +04:00
Ilya Fedin
ccf6a3fb97 Free more disk space in mac action 2025-12-05 09:58:57 +04:00
Ilya Fedin
2005814fca Actually free space in macOS action
Looks like the command was broken all the time but it was unnoticable due to `|| true`
2025-12-05 09:58:57 +04:00
Ilya Fedin
7319665bda Free more disk space in snap action 2025-12-05 09:58:57 +04:00
Ilya Fedin
d74074a21b Stop creating empty TCP log 2025-12-01 19:39:47 +04:00
John Preston
113115f58c Fix selecting gifts for a collection. 2025-12-01 12:20:33 +04:00
John Preston
e84799283d Simplify and fix link wrapping. 2025-12-01 12:20:33 +04:00
John Preston
71272ed2ec Improve view of finished auctions. 2025-12-01 11:38:35 +04:00
Ilya Fedin
6787c338ac Fix Russian bounds
No way it occupies 360° of latitude
2025-12-01 11:30:47 +04:00
Ilya Fedin
774a44ac7e Stop using geocode-glib
It doesn't seem to work properly anymore, it ignores the passed location doing reversed geocoding based on GeoIP only
2025-12-01 11:30:47 +04:00
23rd
29cdd358cc Added regional English/Portuguese spell check support for macOS. 2025-11-29 09:42:12 +03:00
23rd
b9b1bd5e58 Improved once again display of original date in self.
Related commits are 417d151f2c and cbc03d1e45.
2025-11-29 09:17:39 +03:00
23rd
59814aaeb0 Added ability to view stories anonymously from info stories section. 2025-11-29 09:04:42 +03:00
23rd
fd52b3c23b Fixed once again unwanted reset filter on creation of tabs strip.
Regression was introduced in c0bbb669e0.
2025-11-29 08:27:45 +03:00
John Preston
6e75a41ee6 Version 6.3.4: Fix build with Xcode. 2025-11-26 16:44:09 +04:00
John Preston
84266aef2c Version 6.3.4.
- Show active auctions above chats list.
- Add star sending effects in live stories.
- Fix back button in group member profile view.
- Export separate topics message history.
- Export audio files saved to profile.
- Many different crash fixes.
2025-11-26 14:58:32 +04:00
John Preston
40a7f8ea50 Try allowing only tab-focus for screen readers. 2025-11-26 14:57:35 +04:00
John Preston
7c4fcdd9cb Update emoji to Unicode 16. 2025-11-26 14:48:16 +04:00
John Preston
92e87852c1 Fix top bar title/subtitle in saved messages.
Fixes #30018.
2025-11-26 13:27:32 +04:00
John Preston
73c4da2b21 Show '+' to the right of all bids. 2025-11-26 13:01:15 +04:00
23rd
9f4da7e890 Added button for profile color to settings information section. 2025-11-26 05:37:09 +03:00
23rd
8b23457373 Fixed display of verified icon in box from web bot preview.
Regression was introduced in e7fa330215.
2025-11-26 05:30:38 +03:00
23rd
ab2fd7c749 Once again fixed position of tag preview in chats filter settings.
Previous related commit 512e6de39b.
2025-11-26 05:20:23 +03:00
John Preston
040a6ddf3a Fix build with MSVC. 2025-11-25 21:34:26 +04:00
John Preston
55afe0912f Add screen reader state logging. 2025-11-25 21:28:57 +04:00
23rd
ee48127094 Added send small button to box of entry from credits earn history. 2025-11-25 20:01:09 +03:00
23rd
445576d568 Added ToS link to any box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
8f1d40892e Added info of limited gifts to box of entry from credits earn history. 2025-11-25 18:59:47 +03:00
23rd
766db9660c Swap name of sender and subtext for paid messages in earn history list. 2025-11-25 18:59:47 +03:00
23rd
68665ec1f2 Added phrase for paid reactions of live stories to credits earn history. 2025-11-25 18:59:47 +03:00
John Preston
b400964aa1 Don't allow admin to send stars in streams. 2025-11-25 19:37:26 +04:00
John Preston
317530cfa3 Attempt to fix audio cracks in streams. 2025-11-25 19:37:10 +04:00
John Preston
e8fba23b59 Don't allow sending stars from stream admin. 2025-11-25 19:05:55 +04:00
John Preston
ef749e695e Show nice layout of stars-only messages. 2025-11-25 18:30:09 +04:00
John Preston
712ef33d6b Submit custom bid with Enter. 2025-11-25 17:50:25 +04:00
John Preston
0441b7dbc3 Show star senders in live stories. 2025-11-25 17:50:25 +04:00
23rd
3ee0dcbacd Provided topic name to export controller for topic history. 2025-11-25 06:19:41 +03:00
23rd
57411b962f Added initial ability to export topic history. 2025-11-25 06:19:34 +03:00
23rd
4eee00d95e Added forwarded_from_id field to json export for forwarded messages. 2025-11-25 06:14:06 +03:00
23rd
957a08962f Added dark theme to exported html. 2025-11-25 06:14:06 +03:00
23rd
21c82f5fe1 Added ability to view stories anonymously from context menu. 2025-11-25 06:14:06 +03:00
23rd
27964993f6 Added safe guard to userpic in peer qr box. 2025-11-25 06:14:06 +03:00
John Preston
14e296e1f9 Improve layout of about auction box. 2025-11-24 22:06:55 +04:00
John Preston
23c0ff934f Skip premium badge for Saved Messages. 2025-11-24 22:01:56 +04:00
John Preston
c64ef1e20e Allow transfering gifts without premium. 2025-11-24 21:04:51 +04:00
John Preston
ed97619c6c Guard by widget instead of session. 2025-11-24 19:22:20 +04:00
23rd
924ec592b1 Improved paint with ready full peer before animation in profile top bar. 2025-11-24 18:18:13 +03:00
23rd
669c581701 Added dummy filler to PeerQrBox when result link is empty. 2025-11-24 17:46:49 +03:00
23rd
e97ae3d537 Fixed display of QR button in info profile when no usernames. 2025-11-24 17:46:49 +03:00
23rd
08800b68f4 Slightly improved subtext for topic link in info profile. 2025-11-24 17:46:49 +03:00
23rd
a59db6529c Added default guarded callback for simple QR box for peer username. 2025-11-24 17:46:49 +03:00
dependabot[bot]
02a54ceea6 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

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

View File

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

View File

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

View File

@@ -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'

View File

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

View File

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

View File

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

View File

@@ -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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 KiB

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -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 */
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -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";

View File

@@ -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>

View 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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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,

View File

@@ -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(

View File

@@ -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 &,

View File

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

View File

@@ -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(),

View File

@@ -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()),

View File

@@ -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();

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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;

View File

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

View File

@@ -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(

View File

@@ -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 },
};
}

View File

@@ -246,7 +246,8 @@ ChatAdminRightsInfo EditAdminBox::defaultRights() const {
| Flag::DeleteStories
| Flag::InviteByLinkOrAdd
| Flag::ManageCall
| Flag::ManageDirect) };
| Flag::ManageDirect
| Flag::BanUsers) };
}
void EditAdminBox::prepare() {

View File

@@ -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) {

View File

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

View File

@@ -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) },

View File

@@ -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(); });

View File

@@ -74,6 +74,7 @@ enum class PremiumFeature {
FilterTags,
TodoLists,
PeerColors,
Gifts,
// Business features.
BusinessLocation,

View File

@@ -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();

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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 {};
}

View File

@@ -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 {

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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();
});

View File

@@ -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) {

View File

@@ -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,

View File

@@ -1698,6 +1698,7 @@ groupCallMessagesScroll: ScrollArea(defaultScrollArea) {
barHidden: true;
}
groupCallMessagePadding: margins(8px, 3px, 8px, 2px);
groupCallPricePadding: margins(5px, 0px, 5px, 0px);
groupCallMessageSkip: 8px;
groupCallMessagePalette: TextPalette(defaultTextPalette) {
linkFg: radialFg;
@@ -1721,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;

View File

@@ -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());
}

View File

@@ -69,12 +69,6 @@ constexpr auto kStarsStatsShortPollDelay = 30 * crl::time(1000);
return PinFinishDate(message.peer, message.date, message.stars);
}
[[nodiscard]] std::optional<PeerId> MaybeShownPeer(
uint32 privacySet,
PeerId shownPeer) {
return privacySet ? shownPeer : std::optional<PeerId>();
}
} // namespace
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
@@ -104,9 +98,7 @@ Messages::~Messages() {
finishPaidSending({
.count = int(_paid.sending),
.valid = true,
.shownPeer = MaybeShownPeer(
_paid.sendingPrivacySet,
_paid.sendingShownPeer),
.shownPeer = _paid.sendingShownPeer,
}, false);
}
}
@@ -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

View File

@@ -54,18 +54,18 @@ struct MessageDeleteRequest {
bool reportSpam = false;
};
struct StarsTopDonor {
struct StarsDonor {
PeerData *peer = nullptr;
int stars = 0;
bool my = false;
friend inline bool operator==(
const StarsTopDonor &,
const StarsTopDonor &) = default;
const StarsDonor &,
const StarsDonor &) = default;
};
struct StarsTop {
std::vector<StarsTopDonor> topDonors;
std::vector<StarsDonor> topDonors;
int total = 0;
friend inline bool operator==(
@@ -91,7 +91,7 @@ public:
[[nodiscard]] int reactionsPaidScheduled() const;
[[nodiscard]] PeerId reactionsLocalShownPeer() const;
void reactionsPaidAdd(int count, std::optional<PeerId> shownPeer = {});
void reactionsPaidAdd(int count);
void reactionsPaidScheduledCancel();
void reactionsPaidSend();
void undoScheduledPaidOnDestroy();
@@ -101,7 +101,7 @@ public:
int my = 0;
};
[[nodiscard]] PaidLocalState starsLocalState() const;
[[nodiscard]] rpl::producer<> starsValueChanges() const {
[[nodiscard]] rpl::producer<StarsDonor> starsValueChanges() const {
return _paidChanges.events();
}
[[nodiscard]] const StarsTop &starsTop() const {
@@ -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;

View File

@@ -73,6 +73,13 @@ constexpr auto kAdminBadgeTextOpacity = 0.6;
return minHeight / 2;
}
[[nodiscard]] int CountPriceRadius() {
const auto height = st::groupCallPricePadding.top()
+ st::normalFont->height
+ st::groupCallPricePadding.bottom();
return height / 2;
}
[[nodiscard]] int CountPinnedRadius() {
const auto height = st::groupCallUserpicPadding.top()
+ st::groupCallPinnedUserpic
@@ -304,6 +311,7 @@ struct MessagesUi::MessageView {
bool removed = false;
bool sending = false;
bool failed = false;
bool simple = false;
bool admin = false;
bool mine = false;
};
@@ -333,6 +341,7 @@ MessagesUi::PayedBg::PayedBg(const Ui::StarsColoring &coloring)
, pinnedLight(CountPinnedRadius(), light.color())
, pinnedDark(CountPinnedRadius(), dark.color())
, messageLight(CountMessageRadius(), light.color())
, priceDark(CountPriceRadius(), dark.color())
, badgeDark(st::roundRadiusLarge, dark.color()) {
}
@@ -518,6 +527,7 @@ void MessagesUi::animateMessageSent(MessageView &entry) {
void MessagesUi::updateMessageSize(MessageView &entry) {
const auto &padding = st::groupCallMessagePadding;
const auto &pricePadding = st::groupCallPricePadding;
const auto hasUserpic = !entry.failed;
const auto userpicPadding = st::groupCallUserpicPadding;
@@ -532,6 +542,9 @@ void MessagesUi::updateMessageSize(MessageView &entry) {
entry.text,
std::min(st::groupCallWidth / 2, inner),
inner);
const auto price = entry.simple
? (pricePadding.left() + pricePadding.right() + entry.price.maxWidth())
: 0;
const auto space = st::normalFont->spacew;
const auto nameWidth = entry.name.isEmpty() ? 0 : entry.name.maxWidth();
const auto nameLineWidth = nameWidth
@@ -546,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(),
});
}

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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());

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);

View File

@@ -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()) {

View File

@@ -181,3 +181,5 @@ private:
[[nodiscard]] CreditsAmount CreditsAmountFromTL(
const MTPStarsAmount *amount);
[[nodiscard]] MTPStarsAmount StarsAmountToTL(CreditsAmount amount);
[[nodiscard]] QString PrepareCreditsAmountText(CreditsAmount amount);

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 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;

View File

@@ -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());
}

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/gift_auctions.h"
#include "api/api_hash.h"
#include "api/api_premium.h"
#include "api/api_text_entities.h"
#include "apiwrap.h"
@@ -18,6 +19,16 @@ namespace Data {
GiftAuctions::GiftAuctions(not_null<Main::Session*> session)
: _session(session)
, _timer([=] { checkSubscriptions(); }) {
crl::on_main(_session, [=] {
rpl::merge(
_session->data().chatsListChanges(),
_session->data().chatsListLoadedEvents()
) | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
requestActive();
}, _lifetime);
});
}
GiftAuctions::~GiftAuctions() = default;
@@ -50,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

View File

@@ -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

View 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

View 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

View File

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

View File

@@ -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()

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 {

View 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 &param : 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

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