Compare commits

..

204 Commits

Author SHA1 Message Date
John Preston
b41ac0fc2a Version 5.6.2: Fix build with GCC. 2024-10-14 16:34:45 +04:00
John Preston
cfe93530b8 Version 5.6.2.
- Message selection marks.
- Better copy username / public link / ID menu in profiles.
- Keyboard navigation (Up/Down/PageUp/PageDown) in chat preview.
- Improved QR options for profile links.
- Fix calls color problems on Windows.
- Fix scroll problems on Linux.
2024-10-14 13:58:27 +04:00
John Preston
4492e72ffa Fix web app window resize on macOS. 2024-10-14 13:55:29 +04:00
John Preston
1a0fd35f36 Check gift sold out error on sending. 2024-10-14 12:49:55 +04:00
John Preston
db475ef0b4 Update API scheme to layer 190.
Support adding messages to gifts of Premium subscriptions.
2024-10-14 12:49:55 +04:00
John Preston
b3dddc1dfe Fix indent of RTL text with as skip block.
Fixes #27748.
2024-10-14 12:49:54 +04:00
23rd
b64c610abb Fixed ability to copy shared links for channels with select restriction.
Fixed #28492.
2024-10-14 12:49:33 +04:00
23rd
c5add2fca9 Revert "Added experimental option to disable floating reactions strip."
This reverts commit a32a9aa3fc.
2024-10-14 12:49:33 +04:00
23rd
9ea78f7d28 Fixed display of message highlights in selection mode. 2024-10-14 12:49:33 +04:00
23rd
54b0d965ae Slightly eased conditions for selection mode to avoid shaky animations. 2024-10-14 12:49:33 +04:00
23rd
7b4bd5696b Added check views to selection mode of messages. 2024-10-14 12:49:33 +04:00
23rd
9064f3ba4b Fixed behavior of checkbox to remove background from QR in share QR box. 2024-10-14 12:49:33 +04:00
23rd
e00e562b5f Fixed update of width for usernames label in profile section as well. 2024-10-14 12:49:32 +04:00
John Preston
73f38c896f Use new phrase for miniapp launch confirmation. 2024-10-14 12:49:32 +04:00
John Preston
5767cbd0e3 Fix send menus with additional items. 2024-10-14 12:49:32 +04:00
John Preston
ba31756bf9 Fix build with MSVC. 2024-10-14 12:49:32 +04:00
Ilya Fedin
a88f48cd93 Fallback to portal on Linux if QStyleHints::colorScheme is unknown 2024-10-14 12:39:41 +04:00
K900
f2e0e481de Update for lib_ui API change 2024-10-12 19:54:34 +04:00
K900
566f279137 Fix build without QT_IMPLICIT_QCHAR_CONSTRUCTION 2024-10-12 19:54:34 +04:00
John Preston
394ef13955 Fix code block language change.
Fixes #28513.
2024-10-11 09:32:21 +04:00
John Preston
6812e17d07 Improve star gift size in the box. 2024-10-11 09:31:45 +04:00
John Preston
fdf826b686 Improve gifts layout a bit. 2024-10-11 09:31:45 +04:00
John Preston
e57742e7de Don't show non-working wallpaper button. 2024-10-11 09:31:45 +04:00
23rd
73b63aa414 Fixed update of width for username label in profile section. 2024-10-10 18:56:20 +03:00
23rd
44aa2aec5d Added ability to change font size in share QR box. 2024-10-10 18:56:20 +03:00
23rd
8d9d7c4cea Added ability to remove background from QR in share QR box. 2024-10-10 18:56:20 +03:00
23rd
9557f0c844 Improved quality of profile photo in share QR box. 2024-10-10 18:56:20 +03:00
23rd
a32a9aa3fc Added experimental option to disable floating reactions strip. 2024-10-10 18:56:20 +03:00
23rd
86fa98dfbb Fixed position of mini stars in header if gift box. 2024-10-10 18:56:20 +03:00
23rd
6a69447d90 Fixed non focused window when process credits payment from web view bot. 2024-10-10 18:56:20 +03:00
John Preston
2d20e7a9e2 Add keyboard navigation to chat preview. 2024-10-10 14:16:43 +04:00
John Preston
ac7b2e0da0 Keyboard navigation in closed topics.
Fixes #27319.
Fixes #28288.
2024-10-10 14:16:34 +04:00
John Preston
388325a496 Set dpi awareness in the manifest. 2024-10-10 13:53:06 +04:00
John Preston
af728e82fc Unify channel links and usernames clicks. 2024-10-10 13:53:06 +04:00
John Preston
3cb33f0825 Custom "Copy ID" for peer ID context menu. 2024-10-10 13:53:06 +04:00
John Preston
1038baf467 Allow timestamp links under spoilers. 2024-10-10 13:53:05 +04:00
John Preston
828ecabc78 Don't show user badge in star stats rows. 2024-10-10 13:53:05 +04:00
Ilya Fedin
88703ba1eb Toggle fullscreen by double click in media viewe title area 2024-10-10 09:21:29 +02:00
Ilya Fedin
b0ecb2c535 Update patches and lib_ui 2024-10-09 11:34:24 +02:00
Ilya Fedin
c70482dbc4 Qt 6.8.0-rc1 -> 6.8.0 2024-10-09 11:34:24 +02:00
John Preston
51f1999412 Version 5.6.1: Fix build with Xcode. 2024-10-07 15:03:59 +04:00
John Preston
dd4fbc256c Version 5.6.1: Support sold out gifts better. 2024-10-07 14:50:18 +04:00
John Preston
cc2265583f Remove assert in GL init. 2024-10-07 14:49:00 +04:00
John Preston
1e7a4db57f Version 5.6.1.
- Fix media viewer sometimes blocking the app after launch.
2024-10-07 13:43:01 +04:00
John Preston
9a9e30c88b Fix commands list show after scheduled visit. 2024-10-07 13:42:36 +04:00
John Preston
3d98ebff42 Hide layer when showing topics list. 2024-10-07 13:23:10 +04:00
John Preston
a6f4b1ae8e Fix build with Qt 6. 2024-10-07 12:50:20 +04:00
Ilya Fedin
3e6ea8109c Don't treat window controls as continuous 2024-10-07 10:48:56 +02:00
Ilya Fedin
ec407d57a5 Use one side controls in group call 2024-10-07 10:48:56 +02:00
John Preston
838ad66166 Improve hidpi/window geometry init. 2024-10-07 12:48:06 +04:00
23rd
c6bf905253 Slightly improved style of button to install update from dialogs list. 2024-10-06 12:21:23 +03:00
John Preston
8310230582 Version 5.6: Fix builds. 2024-10-05 21:41:22 +04:00
John Preston
1f21af0bdb Version 5.6.
- Gifts for Telegram Stars.
- Mention / hashtag autocomplete in media caption field.
- Resizing the window used for web apps and games.
- "Include muted chats in folder counters" notifications setting.
- Emoji from Unicode 15.1.
2024-10-05 20:46:11 +04:00
John Preston
127e4a5086 Attempt to fix screen dpi changes on Windows. 2024-10-05 20:36:33 +04:00
John Preston
6a30967f23 Attempt to fix media viewer with OpenGL. 2024-10-05 20:33:51 +04:00
John Preston
df2b020b42 Add debug logs for recent/top peers resetting. 2024-10-05 20:32:09 +04:00
23rd
a74c5a89a6 Added manage channel button to profile section of owned channels. 2024-10-05 18:50:03 +03:00
23rd
3cb0be5be5 Added administrators button to profile section of owned channels. 2024-10-05 18:50:03 +03:00
23rd
d56f3cfecf Added credits balance to profile section of owned bots. 2024-10-05 18:50:03 +03:00
23rd
9716a901d1 Added ability to cache credits balance by peer. 2024-10-05 18:50:03 +03:00
23rd
649d242e9a Added userpic of web view bots to box of confirmation to open. 2024-10-05 18:50:03 +03:00
23rd
8ac3c2157f Removed redundant code from swipe-to-reply for macOS. 2024-10-05 18:50:03 +03:00
23rd
7ca4ca21fa Fixed middle elision in filenames from document views. 2024-10-05 18:50:03 +03:00
23rd
e565acba91 Added api support for stories reports with server options. 2024-10-05 18:49:21 +03:00
John Preston
ede771e51b Focus gift message field. 2024-10-04 10:06:21 +04:00
John Preston
0282786b4c Generalize gift sending. 2024-10-04 10:06:21 +04:00
John Preston
42dd08ace5 Use a separate title for star gift recipient box. 2024-10-04 10:06:21 +04:00
John Preston
cf1d274b0d Return gift_credits_box with gifting stars. 2024-10-04 10:06:21 +04:00
John Preston
d361d5f3b2 Move gift_credits_box -> star_gift_box, strip old code. 2024-10-04 10:06:21 +04:00
John Preston
3e63b40564 Server-side star gift message length limit. 2024-10-04 10:06:21 +04:00
John Preston
c5139ed06a Fix emoji panel position in star gift box. 2024-10-04 10:06:21 +04:00
23rd
3dc93526ff Renamed report_box with report_box_graphics. 2024-10-04 10:06:21 +04:00
23rd
3edf8e10e2 Added api support for reports with server options. 2024-10-04 10:06:21 +04:00
John Preston
00215622cc Update API scheme on layer 189. Build broken. 2024-10-04 10:06:21 +04:00
John Preston
479b7c3140 Add emoji selector to star gift sending. 2024-10-04 10:06:21 +04:00
John Preston
a3ca8ddcfc Support entities in star gift message in box. 2024-10-04 10:06:21 +04:00
John Preston
9ace04d2c9 Support entities in star gift messages. 2024-10-04 10:06:21 +04:00
John Preston
8b11d2d5e7 Show nice star gift transactions in history. 2024-10-04 10:06:21 +04:00
John Preston
05fb0f81f9 Show gifts about text. 2024-10-04 10:06:21 +04:00
John Preston
da49efa1ed Simple profile page for Verification Codes. 2024-10-04 10:06:21 +04:00
John Preston
39fb0a5b66 Remove custom PeerData::isVerifyCodes() userpic. 2024-10-04 10:06:21 +04:00
John Preston
962d4d29ee Process gift updates in list. 2024-10-04 10:06:21 +04:00
John Preston
779e9b658b Update API scheme on layer 189. 2024-10-04 10:06:20 +04:00
John Preston
f9ee4dcb51 Show 'hidden from profile' icon in my gifts. 2024-10-04 10:06:20 +04:00
John Preston
1acdbb69ae Show correct gift sender. 2024-10-04 10:06:20 +04:00
John Preston
c022a1c838 Fix gifts list loading both ways. 2024-10-04 10:06:20 +04:00
John Preston
52afd3d5a8 Display gifts list in the profile. 2024-10-04 10:06:20 +04:00
John Preston
79b1c0edee Support PeerData::isVerifyCodes() peer type. 2024-10-04 10:06:20 +04:00
John Preston
4803bd4b3f Started gifts list in profile. 2024-10-04 10:06:20 +04:00
John Preston
215a262076 Allow converting gifts to stars. 2024-10-04 10:06:20 +04:00
John Preston
7a35577d3a Save/unsave gifts to the profile page. 2024-10-04 10:06:20 +04:00
John Preston
efbd3ca8fa Implement star gift view box. 2024-10-04 10:06:20 +04:00
John Preston
18904412cd Show chat and toast about sent gift. 2024-10-04 10:06:20 +04:00
John Preston
0d0d0ab994 Allow sending premium / star gifts. 2024-10-04 10:06:20 +04:00
John Preston
71deef61f5 Add support for star gifts API. 2024-10-04 10:06:20 +04:00
John Preston
46c8a55f56 Make nicer discount/limited gift badges. 2024-10-04 10:06:20 +04:00
John Preston
37f837dcb7 Add sending gift preview. 2024-10-04 10:06:20 +04:00
John Preston
a5be0a685a Extract gift button into separate widget. 2024-10-04 10:06:20 +04:00
John Preston
4cdd1fec95 Start UI of the gifts. 2024-10-04 10:06:20 +04:00
John Preston
761617c1ce Start UI of the new gift sending box. 2024-10-04 10:06:20 +04:00
John Preston
e3bc4dab85 Update API scheme to layer 189. 2024-10-04 10:06:20 +04:00
John Preston
335095a332 Beta version 5.5.8: Fix build with Xcode. 2024-10-03 12:21:06 +04:00
John Preston
15fcb73e19 Beta version 5.5.8.
- Allow drag-to-scroll in reply quote edit box.
- Add bot verified badge to web app title.
- Fix crash when opening some channels.
- Fix some visual glitches.
2024-10-03 11:59:57 +04:00
John Preston
a8b0f2934b Scroll quote selection in the draft options box. 2024-10-03 11:56:04 +04:00
John Preston
7d67b3d00a Use circle userpics in reactions from forums. 2024-10-03 11:55:57 +04:00
John Preston
b60c7e97ab Don't show full query in "nothing found" info. 2024-10-03 11:55:57 +04:00
23rd
440ebfcbf6 Removed display of userpic for sponsored messages. 2024-10-03 11:55:57 +04:00
23rd
8d93eb919b Added verified badge to title in panel for web view bots. 2024-10-03 11:55:55 +04:00
John Preston
d3df2dc1e5 Use Data::MediaPreload in sponsored messages. 2024-10-03 11:54:28 +04:00
John Preston
677314dacb Extract Data::MediaPreload. 2024-10-03 11:54:28 +04:00
John Preston
20f53c89ad Fix poll bottom ripple.
Fixes #28471.
2024-10-03 11:54:28 +04:00
John Preston
1cb92ef69a Beta version 5.5.7: Workaround MSVC bug. 2024-10-01 10:55:42 +04:00
John Preston
b518f7c4c4 Beta version 5.5.7.
- Add ability to share QR code of bots.
- Add mention/hashtag autocomplete to Saved GIF caption.
- Fixed display of working hours in profiles.
- Respect the maximum reply quote length.
2024-10-01 10:06:45 +04:00
John Preston
9fdc099c3a Update lib_webview. 2024-10-01 08:57:59 +04:00
23rd
1db8ada2aa Slightly improved code style of history widget. 2024-10-01 08:57:39 +04:00
23rd
b753448052 Changed behavior for sponsored messages to display them only when ready. 2024-10-01 08:57:38 +04:00
23rd
e29c6d2f23 Fixed infinite attempts to draw foreground in userpic builder. 2024-10-01 08:57:37 +04:00
GitHub Action
1ee53ee0c1 Update User-Agent for DNS to Chrome 129.0.0.0. 2024-10-01 08:52:46 +04:00
John Preston
57438867b6 Respect the maximum quote length. 2024-09-30 14:46:18 +04:00
John Preston
86c04424f6 Add mention/hashtag autocomplete to GIF caption. 2024-09-30 14:46:18 +04:00
John Preston
632639d581 Allow custom tab action on all autocomplete fields.
Fixes #28455.
2024-09-30 14:46:18 +04:00
23rd
99c8edb3eb Fixed display of working hours in profiles. 2024-09-30 13:29:19 +03:00
23rd
4582e61cfc Improved code style in newest code from export submodule. 2024-09-30 13:12:28 +03:00
Bohdan Tkachenko
a970fe93c1 Add reactions to export chat history (#28252)
Co-authored-by: 23rd <23rd@vivaldi.net>
2024-09-30 03:02:22 -07:00
GitHub Action
91f5c72cf0 Update User-Agent for DNS to Chrome 128.0.0.0. 2024-09-30 11:03:36 +02:00
John Preston
d2f3d04ad4 Update cmake/lib_base submodules. 2024-09-30 12:47:22 +04:00
John Preston
07b93e1e02 Fix media editing in topics. 2024-09-30 12:47:22 +04:00
23rd
ab5e7b1588 Fixed color of mini icon for drafts with reply when dialog is selected. 2024-09-30 12:47:22 +04:00
23rd
0f4aadddfc Fixed display of corner status of video in sponsored messages. 2024-09-30 12:47:22 +04:00
John Preston
a3c79feeba Always show My Stars menu. 2024-09-30 12:47:22 +04:00
John Preston
e6b76fefa1 Show correct error for discuss groups. 2024-09-30 12:47:22 +04:00
23rd
a37f512949 Fixed emoji display in filenames from document views. 2024-09-30 12:47:22 +04:00
23rd
3f34c0ce37 Slightly improved style of box for warning of web view box. 2024-09-30 12:47:22 +04:00
23rd
7cc9a0b9aa Added ability to share QR code of bots. 2024-09-30 12:47:22 +04:00
23rd
fbaa79f168 Fixed display of outline in box for share of QR code on non-retina. 2024-09-30 12:47:22 +04:00
Ilya Fedin
2b0aa8e418 Add description for external video player option 2024-09-27 14:37:51 +02:00
Vladislav Grudinin
1fa2e76c9b Updated codegen submodule. 2024-09-27 14:37:29 +02:00
Vladislav Grudinin
cb166e2591 Fix build with clang 19.1.0.
Fixes #28283.
2024-09-27 14:37:29 +02:00
xmdn
187c2dda20 Check more protocols while adding link (#28407) 2024-09-26 10:54:16 -07:00
Ilya Fedin
b962d2b550 Update Qt 6.8.0-beta4 -> 6.8.0-rc1 2024-09-26 16:15:25 +02:00
Ilya Fedin
d69bcfa138 Update lib_webview & lib_ui 2024-09-26 16:15:25 +02:00
c0re100
0960a3b21d Fix restrict user in recent actions 2024-09-19 17:48:37 +03:00
John Preston
aec220fd2c Beta version 5.5.6.
- Allow resizing the window used for web apps and games.
- Add mention / hashtag autocomplete in media caption field.
- Add "Include muted chats in folder counters" notifications setting.
- Auto-select URL suggested from clipboard when creating a custom link.
- Update emoji to Unicode 15.1.
- Fix replying with a quote from media album caption.
- Fix viewing custom emoji packs from media album caption.
2024-09-19 15:02:16 +04:00
John Preston
666dacf73b Add 'tonsite' handler information to lib/xdg/*. 2024-09-19 13:19:50 +04:00
John Preston
67ce27afaa Update patches for Qt 6.8 on Windows. 2024-09-19 12:52:18 +04:00
23rd
3f6d184435 Added api support for clicks on sponsored messages with media. 2024-09-19 12:47:54 +04:00
John Preston
a2fad84dae Update API scheme to layer 188. 2024-09-19 12:47:54 +04:00
John Preston
4a84f9fa00 Allow resizing window of web apps / games. 2024-09-19 12:47:46 +04:00
Ilya Fedin
7abc921d20 Revert "Fullscreen hack for Windows 11 native resize"
This reverts commit f7bff01793.
2024-09-19 10:46:46 +02:00
Ilya Fedin
a307e7a798 Replace fullscreen mask workaround with Qt one 2024-09-19 10:46:46 +02:00
Ilya Fedin
3a9eb8463d Try to always use QWidget::screen 2024-09-19 10:46:10 +02:00
John Preston
4befb125e3 Update emoji to Unicode 15.1. 2024-09-19 12:26:52 +04:00
John Preston
ca00a19736 Updated emoji resources to Unicode 15.1. 2024-09-19 10:26:30 +02:00
John Preston
296969bcd1 Add "Ada" library mention to README. 2024-09-19 10:43:38 +04:00
John Preston
3218c30983 Add "Include muted chats in folder counters". 2024-09-19 10:34:01 +04:00
John Preston
3658b32db5 Correctly count 'other accounts unread' in menu. 2024-09-19 10:34:01 +04:00
John Preston
592d46c8f2 Fix emoji packs from album captions. 2024-09-19 10:34:01 +04:00
John Preston
8b86f12c23 Fix quote-reply to album captions. 2024-09-19 10:34:01 +04:00
John Preston
2e2d8d2af3 Add FieldAutocomplete to EditCaptionBox. 2024-09-19 10:34:01 +04:00
John Preston
3eec43cacd Add FieldAutocomplete to SendFilesBox. 2024-09-19 10:34:01 +04:00
John Preston
ba7cd25f21 Extract FieldAutocomplete code. 2024-09-19 10:34:01 +04:00
John Preston
969152e949 Move FieldAutocomplete to ChatHelpers. 2024-09-19 10:34:01 +04:00
23rd
b78443cf2d Added mini icon to drafts with reply. 2024-09-19 10:34:01 +04:00
23rd
4288ba2449 Improved UX in box for link creation when user has link in clipboard. 2024-09-19 10:34:01 +04:00
Ilya Fedin
22a3093815 Check updater exit status on Linux 2024-09-18 08:27:18 +02:00
John Preston
98ba2c7ce4 Version 5.5.5.
- Suggest URL from clipboard when creating a custom link.
- Fix animated topic icons in topic info page.
- Fix excessive getChannelDifference requests.
2024-09-13 21:04:07 +04:00
John Preston
36962b8c62 Fix possible crash in swipe-to-reply. 2024-09-13 21:04:07 +04:00
John Preston
d0a8bd1f03 Use colored topic icons only when required. 2024-09-13 20:06:38 +04:00
John Preston
c9e0f50f0f Revert "Improved color of topic icon in TopicIconView."
This reverts commit a38dcb6ee5.
2024-09-13 20:06:32 +04:00
John Preston
1dc310586d Unregister channel from shortpoll. 2024-09-13 20:06:19 +04:00
23rd
6f71d21bb7 Slightly improved box for link creation when user has link in clipboard. 2024-09-13 17:14:48 +03:00
Ilya Fedin
c93f047056 Add run0 support 2024-09-12 23:08:06 +04:00
John Preston
ef98d4ece7 Version 5.5.4.
- Fix channel updates stopping after difference request failing.
- Add QR code generation for your username in Settings.
- Fix swipe-to-reply gestures stopping. (macOS)
2024-09-12 14:41:33 +04:00
23rd
2383bf2c71 Fit amount of available colors in box for share of QR code to 8. 2024-09-12 13:39:13 +03:00
23rd
2c8c92c2a4 Slightly increased max width for username in QR code. 2024-09-12 13:38:36 +03:00
23rd
bad2dc30c3 Added button to generate QR code for self to main section of settings. 2024-09-12 13:28:10 +03:00
Ilya Fedin
fe7f4233b9 Add missing ThirdParty cache to Windows action 2024-09-12 14:13:53 +04:00
Ilya Fedin
68454a9841 Migrate almost all actions to latest OS versions 2024-09-12 14:13:53 +04:00
John Preston
d3bcf63cf7 Fix repeated request of channel difference. 2024-09-12 14:08:09 +04:00
John Preston
a5f1209f28 Remove redundant web apps code. 2024-09-12 14:08:09 +04:00
23rd
9a44ca2769 Fixed display of message without text in section of shared links. 2024-09-11 16:31:46 +03:00
23rd
155305f0f7 Added ability to generate QR code for self from settings. 2024-09-11 15:25:05 +03:00
23rd
6abce8d976 Added attention style to menu item to delete all call logs. 2024-09-11 15:25:05 +03:00
23rd
f16d1f034f Switched swipe-to-reply processing on macOS from touch to scroll events. 2024-09-11 00:37:47 +03:00
John Preston
dab107cf90 Version 5.5.3.
- Fix custom emoji sending.
- Add mention link QR code sharing.
- Fix sending files from network drives. (Windows)
2024-09-10 15:07:14 +04:00
23rd
d1d1aa3d21 Moved premium gradient in box for share of QR code to first place. 2024-09-10 13:51:23 +03:00
23rd
d0536cc31f Replaced boxes for QR code of invite links with box for share QR code. 2024-09-10 13:51:23 +03:00
23rd
c47f5e9995 Added ability to create box for share QR code without peer. 2024-09-10 13:51:23 +03:00
23rd
0916836ff9 Added ability to provide description and custom link to FillPeerQrBox. 2024-09-10 13:51:23 +03:00
John Preston
5dd9ff1062 Accept only CF_HDROP for uri-list mime.
Additional types lead to such problem: some programs, like JetBrains
IDEs, like PyCharm, copy "file://[ip-address]/file" as an URI in
uri-list, that way you can't paste it in the messenger, because it
will make a request to that address and reveal clients IP address.
2024-09-10 14:19:37 +04:00
John Preston
d1e3b9f15d Revert "Check for local URLs more strictly."
This reverts commit c3fda41224.
2024-09-10 14:19:37 +04:00
John Preston
ca3c179b75 Use full available width for text in some places.
Fixes #28384.
2024-09-10 14:19:37 +04:00
John Preston
c2d5924508 Fix build with MSVC. 2024-09-10 14:19:37 +04:00
John Preston
016d0395c3 Fix custom emoji sending. 2024-09-10 14:17:32 +04:00
23rd
925c9239bd Added ability to share QR code from channels. 2024-09-10 11:34:55 +03:00
23rd
fdcbe3cf3a Fixed width of username label with right button in profile section. 2024-09-10 11:08:42 +03:00
23rd
f6b9cc5ce1 Improved padding in box for share QR box on different scales. 2024-09-10 10:56:02 +03:00
23rd
8c915e6dc3 Renamed FillProfileQrBox to FillPeerQrBox. 2024-09-10 03:26:15 +03:00
23rd
1db426da2e Added ability to choose quality in box for share of QR code. 2024-09-10 03:26:15 +03:00
23rd
d9be363962 Added ability to hide userpic from box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
88daa37e34 Added ability to generate QR code independently from ui scale. 2024-09-10 02:00:26 +03:00
23rd
931fa01337 Added premium gradient to box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
8e1595eb29 Adapted display of longest usernames in box for share of QR code. 2024-09-10 02:00:26 +03:00
23rd
29e97232d8 Added initial ability to share QR code from user profile. 2024-09-10 02:00:26 +03:00
295 changed files with 9832 additions and 3109 deletions

View File

@@ -40,7 +40,7 @@ jobs:
macos:
name: MacOS
runs-on: macos-13
runs-on: macos-latest
strategy:
matrix:
@@ -49,7 +49,7 @@ jobs:
env:
GIT: "https://github.com"
CMAKE_PREFIX_PATH: "/usr/local/opt/ffmpeg@6:/usr/local/opt/openal-soft"
CMAKE_PREFIX_PATH: "/opt/homebrew/opt/ffmpeg@6:/opt/homebrew/opt/openal-soft"
UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false"
MANUAL_CACHING: "1"
@@ -69,7 +69,7 @@ jobs:
run: |
brew update
brew upgrade || true
brew install ada-url autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version > CACHE_KEY.txt
@@ -95,7 +95,7 @@ jobs:
./autogen.sh
./configure --disable-examples --disable-doc
make -j$(sysctl -n hw.logicalcpu)
make install
sudo make install
- name: WebRTC cache.
id: cache-webrtc
@@ -111,7 +111,11 @@ jobs:
git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git
cd tg_owt
cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug
cmake -Bbuild -GNinja . \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS_DEBUG="" \
-DCMAKE_CXX_FLAGS_DEBUG=""
cmake --build build --parallel
- name: Telegram Desktop build.
@@ -132,6 +136,9 @@ jobs:
cmake -Bbuild -GNinja . \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS_DEBUG="" \
-DCMAKE_CXX_FLAGS_DEBUG="" \
-DCMAKE_EXE_LINKER_FLAGS="-s" \
-DCMAKE_FIND_FRAMEWORK=LAST \
-DTDESKTOP_API_TEST=ON \
-DDESKTOP_APP_USE_PACKAGED_LAZY=ON \

View File

@@ -40,7 +40,7 @@ jobs:
snap:
name: Ubuntu
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
env:
UPLOAD_ARTIFACT: "true"

View File

@@ -42,7 +42,7 @@ jobs:
windows:
name: Windows
runs-on: windows-2022
runs-on: windows-latest
strategy:
matrix:
@@ -94,6 +94,14 @@ jobs:
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
- name: ThirdParty cache.
id: cache-third-party
uses: actions/cache@v4
with:
path: ${{ env.TBUILD }}\ThirdParty
key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }}
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party-
- name: Libraries cache.
id: cache-libs
uses: actions/cache@v4

View File

@@ -64,6 +64,7 @@ Version **1.8.15** was the last that supports older systems
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
* Ada ([Apache License 2.0](https://github.com/ada-url/ada/blob/main/LICENSE-APACHE))
## Build instructions

View File

@@ -322,6 +322,8 @@ PRIVATE
boxes/sessions_box.h
boxes/share_box.cpp
boxes/share_box.h
boxes/star_gift_box.cpp
boxes/star_gift_box.h
boxes/sticker_set_box.cpp
boxes/sticker_set_box.h
boxes/stickers_box.cpp
@@ -569,6 +571,8 @@ PRIVATE
data/data_lastseen_status.h
data/data_location.cpp
data/data_location.h
data/data_media_preload.cpp
data/data_media_preload.h
data/data_media_rotation.cpp
data/data_media_rotation.h
data/data_media_types.cpp
@@ -604,6 +608,7 @@ PRIVATE
data/data_replies_list.h
data/data_reply_preview.cpp
data/data_reply_preview.h
data/data_report.h
data/data_saved_messages.cpp
data/data_saved_messages.h
data/data_saved_sublist.cpp
@@ -947,6 +952,10 @@ PRIVATE
info/media/info_media_widget.h
info/members/info_members_widget.cpp
info/members/info_members_widget.h
info/peer_gifts/info_peer_gifts_common.cpp
info/peer_gifts/info_peer_gifts_common.h
info/peer_gifts/info_peer_gifts_widget.cpp
info/peer_gifts/info_peer_gifts_widget.h
info/polls/info_polls_results_inner_widget.cpp
info/polls/info_polls_results_inner_widget.h
info/polls/info_polls_results_widget.cpp
@@ -1482,6 +1491,8 @@ PRIVATE
support/support_templates.h
ui/boxes/edit_invite_link_session.cpp
ui/boxes/edit_invite_link_session.h
ui/boxes/peer_qr_box.cpp
ui/boxes/peer_qr_box.h
ui/chat/attach/attach_item_single_file_preview.cpp
ui/chat/attach/attach_item_single_file_preview.h
ui/chat/attach/attach_item_single_media_preview.cpp

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.2 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: 832 KiB

After

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

@@ -582,3 +582,55 @@ div.toast_shown {
.bot_button_column_separator {
width: 2px
}
.reactions {
margin: 5px 0;
}
.reactions .reaction {
display: inline-flex;
height: 20px;
border-radius: 15px;
background-color: #e8f5fc;
color: #168acd;
font-weight: bold;
margin-bottom: 5px;
}
.reactions .reaction.active {
background-color: #40a6e2;
color: #fff;
}
.reactions .reaction.paid {
background-color: #fdf6e1;
color: #c58523;
}
.reactions .reaction.active.paid {
background-color: #ecae0a;
color: #fdf6e1;
}
.reactions .reaction .emoji {
line-height: 20px;
margin: 0 5px;
font-size: 15px;
}
.reactions .reaction .userpic:not(:first-child) {
margin-left: -8px;
}
.reactions .reaction .userpic {
display: inline-block;
}
.reactions .reaction .userpic .initials {
font-size: 8px;
}
.reactions .reaction .count {
margin-right: 8px;
line-height: 20px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -453,6 +453,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_username_app_not_found" = "Bot application not found.";
"lng_username_link" = "This link opens a chat with you:";
"lng_username_copied" = "Link copied to clipboard.";
"lng_username_text_copied" = "Username copied to clipboard.";
"lng_usernames_edit" = "click to edit";
"lng_usernames_active" = "active";
@@ -487,6 +488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
"lng_collectible_phone_copy" = "Copy Phone Number";
"lng_collectible_learn_more" = "Learn More";
"lng_collectible_phone_copied" = "Phone number copied to clipboard.";
"lng_settings_section_info" = "Info";
@@ -508,6 +510,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_alert_linux" = "Draw attention to the window";
"lng_settings_badge_title" = "Badge counter";
"lng_settings_include_muted" = "Include muted chats in unread count";
"lng_settings_include_muted_folders" = "Include muted chats in folder counters";
"lng_settings_count_unread" = "Count unread messages";
"lng_settings_events_title" = "Events";
"lng_settings_events_joined" = "Contact joined Telegram";
@@ -1322,6 +1325,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_similar_channels#other" = "{count} similar channels";
"lng_profile_saved_messages#one" = "{count} saved message";
"lng_profile_saved_messages#other" = "{count} saved messages";
"lng_profile_peer_gifts#one" = "{count} gift";
"lng_profile_peer_gifts#other" = "{count} gifts";
"lng_profile_participants_section" = "Members";
"lng_profile_subscribers_section" = "Subscribers";
"lng_profile_add_contact" = "Add Contact";
@@ -1376,6 +1381,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_copy_fullname" = "Copy Name";
"lng_profile_photo_by_you" = "photo set by you";
"lng_profile_public_photo" = "public photo";
"lng_profile_administrators#one" = "{count} administrator";
"lng_profile_administrators#other" = "{count} administrators";
"lng_profile_manage" = "Channel settings";
"lng_invite_upgrade_title" = "Upgrade to Premium";
"lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users.";
@@ -1659,6 +1667,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_and_ban_button" = "Ban user";
"lng_report_details_about" = "Please enter any additional details relevant to your report.";
"lng_report_details" = "Additional Details";
"lng_report_details_optional" = "Add Comment (Optional)";
"lng_report_details_non_optional" = "Add Comment";
"lng_report_details_message_about" = "Please help us by telling what is wrong with the message you have selected";
"lng_report_reason_spam" = "Spam";
"lng_report_reason_fake" = "Fake Account";
"lng_report_reason_violence" = "Violence";
@@ -1852,8 +1863,19 @@ 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_me" = "You sent to {user} a gift for {cost}";
"lng_action_gift_sent" = "You sent a gift for {cost}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_gift_for_stars#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars";
"lng_action_gift_got_subtitle" = "Gift from {user}";
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
"lng_action_gift_sent_subtitle" = "Gift for {user}";
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
"lng_action_gift_premium_months#one" = "{count} Month Premium";
"lng_action_gift_premium_months#other" = "{count} Months Premium";
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
"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";
@@ -1915,6 +1937,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_similar_channels_premium_all_link" = "Telegram Premium";
"lng_similar_channels_show_more" = "Show more channels";
"lng_peer_gifts_title" = "Gifts";
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
"lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months";
"lng_premium_gift_duration_years#one" = "for {count} year";
@@ -2415,6 +2441,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_gift_name" = "Received Gift";
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
@@ -2461,6 +2488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}";
@@ -2980,6 +3008,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
"lng_gift_until" = "Until";
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
"lng_gift_premium_subtitle" = "Gift Premium";
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
"lng_gift_premium_features" = "See Features >";
"lng_gift_premium_label" = "Premium";
"lng_gift_stars_subtitle" = "Gift Stars";
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
"lng_gift_stars_link" = "What are Stars >";
"lng_gift_stars_limited" = "limited";
"lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_send_title" = "Send a Gift";
"lng_gift_send_message" = "Enter Message";
"lng_gift_send_anonymous" = "Hide My Name";
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
"lng_gift_send_premium_about" = "Only {user} will see your message.";
"lng_gift_send_button" = "Send a Gift for {cost}";
"lng_gift_sent_title" = "Gift Sent!";
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
"lng_gift_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User";
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
"lng_gift_display_on_page" = "Display on my Page";
"lng_gift_display_on_page_hide" = "Hide from my Page";
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone.";
"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone.";
"lng_gift_convert_sure" = "Convert";
"lng_gift_display_done" = "The gift is now shown on your profile page.";
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
"lng_gift_sold_out_title" = "Sold Out!";
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
@@ -3222,6 +3297,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_replies_discussion_started" = "Discussion started";
"lng_replies_no_comments" = "No comments here yet...";
"lng_verification_codes" = "Verification Codes";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
"lng_archived_remove" = "Unarchive";
@@ -3246,7 +3323,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_open_link" = "Open";
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID (not your phone number) to the web pages you open via this bot?";
"lng_allow_bot" = "Allow";
"lng_allow_bot_webview" = "{bot_name} would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info.";
"lng_allow_bot_webview_details" = "More about this bot {emoji}";
"lng_allow_bot_webview_details_about" = "To launch this web app, you will connect to its website.\n\nIt will be able to access your **IP address** and basic device info.";
"lng_url_auth_open_confirm" = "Do you want to open {link}?";
"lng_url_auth_login_option" = "Log in to {domain} as {user}";
"lng_url_auth_allow_messages" = "Allow {bot} to send me messages";
@@ -3608,6 +3686,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reply_header_short" = "Reply";
"lng_reply_quote_selected" = "Quote Selected";
"lng_reply_from_private_chat" = "This reply is from a private chat.";
"lng_reply_quote_long_title" = "Quote too long!";
"lng_reply_quote_long_text" = "The selected text is too long to quote.";
"lng_link_options_header" = "Link Preview Settings";
"lng_link_header_short" = "Link";
"lng_link_move_up" = "Move Up";
@@ -4233,6 +4313,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_restriction_for_all" = "This option is disabled for all members in Group Permissions.";
"lng_rights_permission_for_all" = "This option is enabled for all members in Group Permissions.";
"lng_rights_permission_unavailable" = "This permission is not available in public groups.";
"lng_rights_permission_in_discuss" = "This permission is not available in discussion groups.";
"lng_rights_permission_cant_edit" = "You cannot change this permission.";
"lng_rights_user_restrictions" = "User permissions";
"lng_rights_user_restrictions_header" = "What can this member do?";
@@ -5546,6 +5627,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_contact_details_birthday" = "Birthday";
"lng_contact_details_organization" = "Organization";
"lng_qr_box_quality" = "Quality";
"lng_qr_box_quality1" = "Normal";
"lng_qr_box_quality2" = "High";
"lng_qr_box_quality3" = "Very High";
"lng_qr_box_transparent_background" = "Transparent Background";
"lng_qr_box_font_size" = "Font size";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -31,6 +31,7 @@
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
</qresource>
<qresource prefix="/icons">
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.5.2.0" />
Version="5.6.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -16,6 +16,8 @@
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,0
FILEVERSION 5,6,2,0
PRODUCTVERSION 5,6,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "5.5.2.0"
VALUE "FileVersion", "5.6.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.2.0"
VALUE "ProductVersion", "5.6.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,5,2,0
PRODUCTVERSION 5,5,2,0
FILEVERSION 5,6,2,0
PRODUCTVERSION 5,6,2,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", "5.5.2.0"
VALUE "FileVersion", "5.6.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "5.5.2.0"
VALUE "ProductVersion", "5.6.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -39,6 +39,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace Api {
namespace {
@@ -503,11 +506,19 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
bot->session().attachWebView().open({
.bot = bot,
.context = { .controller = controller },
.button = {.text = button->text, .url = button->data },
.button = { .text = button->text, .url = button->data },
.source = InlineBots::WebViewSourceButton{ .simple = true },
});
}
} break;
case ButtonType::CopyText: {
const auto text = QString::fromUtf8(button->data);
if (!text.isEmpty()) {
QGuiApplication::clipboard()->setText(text);
controller->showToast(tr::lng_text_copied(tr::now));
}
} break;
}
}

View File

@@ -275,7 +275,6 @@ void ConfirmSubscriptionBox(
: 0;
state->api->request(
MTPpayments_SendStarsForm(
MTP_flags(0),
MTP_long(formId),
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
).done([=](const MTPpayments_PaymentResult &result) {

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_updates.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/components/credits.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer.h"
@@ -69,10 +70,12 @@ constexpr auto kTransactionsLimit = 100;
}, [](const auto &) {
return PeerId(0);
}).value;
const auto stargift = tl.data().vstargift();
const auto incoming = (int64(tl.data().vstars().v) >= 0);
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()),
.description = qs(tl.data().vdescription().value_or_empty()),
.description = { qs(tl.data().vdescription().value_or_empty()) },
.date = base::unixtime::parse(tl.data().vdate().v),
.photoId = photo ? photo->id : 0,
.extended = std::move(extended),
@@ -81,6 +84,9 @@ constexpr auto kTransactionsLimit = 100;
.barePeerId = barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = (stargift
? owner->processDocument(stargift->data().vsticker())->id
: 0),
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -104,12 +110,16 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.convertStars = int(stargift
? stargift->data().vconvert_stars().v
: 0),
.converted = stargift && incoming,
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
.failed = tl.data().is_failed(),
.in = (int64(tl.data().vstars().v) >= 0),
.gift = tl.data().is_gift(),
.in = incoming,
.gift = tl.data().is_gift() || stargift.has_value(),
};
}
@@ -239,6 +249,8 @@ void CreditsStatus::request(
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
)).done([=](const TLResult &result) {
_requestId = 0;
const auto balance = result.data().vbalance().v;
_peer->session().credits().apply(_peer->id, balance);
if (const auto onstack = done) {
onstack(StatusFromTL(result, _peer));
}

View File

@@ -37,7 +37,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
MTP_int(dimensions.width()),
MTP_int(dimensions.height()),
MTPint(), // preload_prefix_size
MTPdouble())); // video_start_ts
MTPdouble(), // video_start_ts
MTPstring())); // video_codec
} else {
attributes.push_back(MTP_documentAttributeImageSize(
MTP_int(dimensions.width()),

View File

@@ -550,6 +550,24 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
};
}
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
auto result = std::vector<GiftOptionData>();
if (!_optionsForOnePerson.currency.isEmpty()) {
const auto count = int(_optionsForOnePerson.months.size());
result.reserve(count);
for (auto i = 0; i != count; ++i) {
Assert(i < _optionsForOnePerson.totalCosts.size());
result.push_back({
.cost = _optionsForOnePerson.totalCosts[i],
.currency = _optionsForOnePerson.currency,
.months = _optionsForOnePerson.months[i],
});
}
}
return result;
}
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
const auto it = _subscriptionOptions.find(amount);
if (it != end(_subscriptionOptions)) {
@@ -571,6 +589,41 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
}
}
auto PremiumGiftCodeOptions::requestStarGifts()
-> rpl::producer<rpl::no_value, QString> {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
_api.request(MTPpayments_GetStarGifts(
MTP_int(0)
)).done([=](const MTPpayments_StarGifts &result) {
result.match([&](const MTPDpayments_starGifts &data) {
_giftsHash = data.vhash().v;
const auto &list = data.vgifts().v;
const auto session = &_peer->session();
auto gifts = std::vector<StarGift>();
gifts.reserve(list.size());
for (const auto &gift : list) {
if (auto parsed = FromTL(session, gift)) {
gifts.push_back(std::move(*parsed));
}
}
_gifts = std::move(gifts);
}, [&](const MTPDpayments_starGiftsNotModified &) {
});
consumer.put_done();
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
}).send();
return lifetime;
};
}
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
return _gifts;
}
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
constexpr auto kFallbackCount = 4;
return _peer->session().appConfig().get<int>(
@@ -705,4 +758,56 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
}) | rpl::take(1) | rpl::map(random));
}
std::optional<StarGift> FromTL(
not_null<Main::Session*> session,
const MTPstarGift &gift) {
const auto &data = gift.data();
const auto document = session->data().processDocument(
data.vsticker());
const auto remaining = data.vavailability_remains();
const auto total = data.vavailability_total();
if (!document->sticker()) {
return {};
}
return StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
.convertStars = int64(data.vconvert_stars().v),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
};
}
std::optional<UserStarGift> FromTL(
not_null<UserData*> to,
const MTPuserStarGift &gift) {
const auto session = &to->session();
const auto &data = gift.data();
auto parsed = FromTL(session, data.vgift());
if (!parsed) {
return {};
}
return UserStarGift{
.gift = std::move(*parsed),
.message = (data.vmessage()
? TextWithEntities{
.text = qs(data.vmessage()->data().vtext()),
.entities = Api::EntitiesFromMTP(
session,
data.vmessage()->data().ventities().v),
}
: TextWithEntities()),
.convertStars = int64(data.vconvert_stars().value_or_empty()),
.fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v)
: PeerId()),
.messageId = data.vmsg_id().value_or_empty(),
.date = data.vdate().v,
.anonymous = data.is_name_hidden(),
.hidden = data.is_unsaved(),
.mine = to->isSelf(),
};
}
} // namespace Api

View File

@@ -67,6 +67,33 @@ struct GiveawayInfo {
}
};
struct GiftOptionData {
int64 cost = 0;
QString currency;
int months = 0;
};
struct StarGift {
uint64 id = 0;
int64 stars = 0;
int64 convertStars = 0;
not_null<DocumentData*> document;
int limitedLeft = 0;
int limitedCount = 0;
};
struct UserStarGift {
StarGift gift;
TextWithEntities message;
int64 convertStars = 0;
PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0;
bool anonymous = false;
bool hidden = false;
bool mine = false;
};
class Premium final {
public:
explicit Premium(not_null<ApiWrap*> api);
@@ -171,6 +198,7 @@ public:
PremiumGiftCodeOptions(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
[[nodiscard]] std::vector<GiftOptionData> optionsForPeer() const;
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
[[nodiscard]] const std::vector<int> &availablePresets() const;
[[nodiscard]] int monthsFromPreset(int monthsIndex);
@@ -187,6 +215,9 @@ public:
[[nodiscard]] int giveawayPeriodMax() const;
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
private:
struct Token final {
int users = 0;
@@ -206,7 +237,7 @@ private:
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
struct {
std::vector<int> months;
std::vector<float64> totalCosts;
std::vector<int64> totalCosts;
QString currency;
} _optionsForOnePerson;
@@ -214,6 +245,9 @@ private:
base::flat_map<Token, Store> _stores;
int32 _giftsHash = 0;
std::vector<StarGift> _gifts;
MTP::Sender _api;
};
@@ -242,4 +276,11 @@ enum class RequirePremiumState {
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
not_null<Main::Session*> session);
[[nodiscard]] std::optional<StarGift> FromTL(
not_null<Main::Session*> session,
const MTPstarGift &gift);
[[nodiscard]] std::optional<UserStarGift> FromTL(
not_null<UserData*> to,
const MTPuserStarGift &gift);
} // namespace Api

View File

@@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_report.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/report_box.h"
#include "ui/boxes/report_box_graphics.h"
#include "ui/layers/show.h"
namespace Api {
@@ -40,15 +41,11 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
} // namespace
void SendReport(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
Ui::ReportReason reason,
const QString &comment,
std::variant<
v::null_t,
MessageIdsList,
not_null<PhotoData*>,
StoryId> data) {
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
Ui::ReportReason reason,
const QString &comment,
std::variant<v::null_t, not_null<PhotoData*>> data) {
auto done = [=] {
show->showToast(tr::lng_report_thanks(tr::now));
};
@@ -58,18 +55,6 @@ void SendReport(
ReasonToTL(reason),
MTP_string(comment)
)).done(std::move(done)).send();
}, [&](const MessageIdsList &ids) {
auto apiIds = QVector<MTPint>();
apiIds.reserve(ids.size());
for (const auto &fullId : ids) {
apiIds.push_back(MTP_int(fullId.msg));
}
peer->session().api().request(MTPmessages_Report(
peer->input,
MTP_vector<MTPint>(apiIds),
ReasonToTL(reason),
MTP_string(comment)
)).done(std::move(done)).send();
}, [&](not_null<PhotoData*> photo) {
peer->session().api().request(MTPaccount_ReportProfilePhoto(
peer->input,
@@ -77,14 +62,93 @@ void SendReport(
ReasonToTL(reason),
MTP_string(comment)
)).done(std::move(done)).send();
}, [&](StoryId id) {
peer->session().api().request(MTPstories_Report(
peer->input,
MTP_vector<MTPint>(1, MTP_int(id)),
ReasonToTL(reason),
MTP_string(comment)
)).done(std::move(done)).send();
});
}
auto CreateReportMessagesOrStoriesCallback(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer)
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)> {
using TLChoose = MTPDreportResultChooseOption;
using TLAddComment = MTPDreportResultAddComment;
using TLReported = MTPDreportResultReported;
using Result = ReportResult;
struct State final {
#ifdef _DEBUG
~State() {
qDebug() << "Messages or Stories Report ~State().";
}
#endif
mtpRequestId requestId = 0;
};
const auto state = std::make_shared<State>();
return [=](
Data::ReportInput reportInput,
Fn<void(Result)> done) {
auto apiIds = QVector<MTPint>();
apiIds.reserve(reportInput.ids.size() + reportInput.stories.size());
for (const auto &id : reportInput.ids) {
apiIds.push_back(MTP_int(id));
}
for (const auto &story : reportInput.stories) {
apiIds.push_back(MTP_int(story));
}
const auto received = [=](
const MTPReportResult &result,
mtpRequestId requestId) {
if (state->requestId != requestId) {
return;
}
state->requestId = 0;
done(result.match([&](const TLChoose &data) {
const auto t = qs(data.vtitle());
auto list = Result::Options();
list.reserve(data.voptions().v.size());
for (const auto &tl : data.voptions().v) {
list.emplace_back(Result::Option{
.id = tl.data().voption().v,
.text = qs(tl.data().vtext()),
});
}
return Result{ .options = std::move(list), .title = t };
}, [&](const TLAddComment &data) -> Result {
return {
.commentOption = ReportResult::CommentOption{
.optional = data.is_optional(),
.id = data.voption().v,
}
};
}, [&](const TLReported &data) -> Result {
return { .successful = true };
}));
};
const auto fail = [=](const MTP::Error &error) {
state->requestId = 0;
done({ .error = error.type() });
};
if (!reportInput.stories.empty()) {
state->requestId = peer->session().api().request(
MTPstories_Report(
peer->input,
MTP_vector<MTPint>(apiIds),
MTP_bytes(reportInput.optionId),
MTP_string(reportInput.comment))
).done(received).fail(fail).send();
} else {
state->requestId = peer->session().api().request(
MTPmessages_Report(
peer->input,
MTP_vector<MTPint>(apiIds),
MTP_bytes(reportInput.optionId),
MTP_string(reportInput.comment))
).done(received).fail(fail).send();
}
};
}
} // namespace Api

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class HistoryItem;
class PeerData;
class PhotoData;
@@ -15,17 +16,41 @@ class Show;
enum class ReportReason;
} // namespace Ui
namespace Data {
struct ReportInput;
} // namespace Data
namespace Api {
struct ReportResult final {
using Id = QByteArray;
struct Option final {
Id id = 0;
QString text;
};
using Options = std::vector<Option>;
Options options;
QString title;
QString error;
QString comment;
struct CommentOption {
bool optional = false;
Id id = 0;
};
std::optional<CommentOption> commentOption;
bool successful = false;
};
void SendReport(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
Ui::ReportReason reason,
const QString &comment,
std::variant<
v::null_t,
MessageIdsList,
not_null<PhotoData*>,
StoryId> data);
std::variant<v::null_t, not_null<PhotoData*>> data);
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer)
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
} // namespace Api

View File

@@ -547,7 +547,7 @@ void SendConfirmedFile(
MTP_flags(Flag::f_document
| (file->spoiler ? Flag::f_spoiler : Flag())),
file->document,
MTPDocument(), // alt_document
MTPVector<MTPDocument>(), // alt_documents
MTPint());
} else if (file->type == SendMediaType::Audio) {
const auto ttlSeconds = file->to.options.ttlSeconds;
@@ -572,7 +572,7 @@ void SendConfirmedFile(
| (isVoice ? Flag::f_voice : Flag())
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
file->document,
MTPDocument(), // alt_document
MTPVector<MTPDocument>(), // alt_documents
MTP_int(ttlSeconds));
} else {
Unexpected("Type in sendFilesConfirmed.");

View File

@@ -417,13 +417,12 @@ void Updates::channelDifferenceDone(
"{ good - after not final channelDifference was received }%1"
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
getChannelDifference(channel);
} else if (ranges::contains(
_activeChats,
channel,
[](const auto &pair) { return pair.second.peer; })) {
channel->ptsWaitingForShortPoll(timeout
} else if (inActiveChats(channel)) {
channel->ptsSetWaitingForShortPoll(timeout
? (timeout * crl::time(1000))
: kWaitForChannelGetDifference);
} else {
channel->ptsSetWaitingForShortPoll(-1);
}
}
@@ -655,6 +654,7 @@ void Updates::getDifferenceAfterFail() {
wait = wait ? std::min(wait, i->second - now) : (i->second - now);
++i;
} else {
i->first->ptsSetRequesting(false);
getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail);
i = _whenGetDiffAfterFail.erase(i);
}
@@ -703,7 +703,9 @@ void Updates::getChannelDifference(
_whenGetDiffByPts.remove(channel);
}
if (!channel->ptsInited() || channel->ptsRequesting()) return;
if (!channel->ptsInited() || channel->ptsRequesting()) {
return;
}
if (from != ChannelDifferenceRequest::AfterFail) {
_whenGetDiffAfterFail.remove(channel);
@@ -740,16 +742,32 @@ void Updates::addActiveChat(rpl::producer<PeerData*> chat) {
std::move(
chat
) | rpl::start_with_next_done([=](PeerData *peer) {
_activeChats[key].peer = peer;
if (const auto channel = peer ? peer->asChannel() : nullptr) {
channel->ptsWaitingForShortPoll(
kWaitForChannelGetDifference);
auto &active = _activeChats[key];
const auto was = active.peer;
if (was != peer) {
active.peer = peer;
if (const auto channel = was ? was->asChannel() : nullptr) {
if (!inActiveChats(channel)) {
channel->ptsSetWaitingForShortPoll(-1);
}
}
if (const auto channel = peer ? peer->asChannel() : nullptr) {
channel->ptsSetWaitingForShortPoll(
kWaitForChannelGetDifference);
}
}
}, [=] {
_activeChats.erase(key);
}, _activeChats[key].lifetime);
}
bool Updates::inActiveChats(not_null<PeerData*> peer) const {
return ranges::contains(
_activeChats,
peer.get(),
[](const auto &pair) { return pair.second.peer; });
}
void Updates::requestChannelRangeDifference(not_null<History*> history) {
Expects(history->peer->isChannel());
@@ -1554,6 +1572,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
if (channel && !_handlingChannelDifference) {
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
MTP_LOG(0, ("Skipping new channel message because getting the difference."));
return;
}
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
@@ -1646,6 +1665,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (channel && !_handlingChannelDifference) {
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
MTP_LOG(0, ("Skipping channel message edit because getting the difference."));
return;
} else {
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
@@ -1661,6 +1681,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (channel && !_handlingChannelDifference) {
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
MTP_LOG(0, ("Skipping pinned channel messages because getting the difference."));
return;
} else {
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
@@ -1775,6 +1796,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
if (channel && !_handlingChannelDifference) {
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
MTP_LOG(0, ("Skipping delete channel messages because getting the difference."));
return;
}
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
@@ -1838,6 +1860,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
auto channel = session().data().channelLoaded(d.vchannel_id());
if (channel && !_handlingChannelDifference) {
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
MTP_LOG(0, ("Skipping channel web page update because getting the difference."));
return;
} else {
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);

View File

@@ -63,6 +63,7 @@ public:
void requestChannelRangeDifference(not_null<History*> history);
void addActiveChat(rpl::producer<PeerData*> chat);
[[nodiscard]] bool inActiveChats(not_null<PeerData*> peer) const;
private:
enum class ChannelDifferenceRequest {

View File

@@ -2028,7 +2028,7 @@ void ApiWrap::deleteHistory(
}
if (const auto channel = peer->asChannel()) {
if (!justClear && !revoke) {
channel->ptsWaitingForShortPoll(-1);
channel->ptsSetWaitingForShortPoll(-1);
leaveChannel(channel);
} else {
if (const auto migrated = peer->migrateFrom()) {

View File

@@ -1120,3 +1120,10 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
selectLinkFg: windowActiveTextFg;
}
}
profileQrFont: font(fsize bold);
profileQrCenterSize: 34px;
profileQrBackgroundRadius: 12px;
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/field_autocomplete.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
@@ -371,6 +372,14 @@ void EditCaptionBox::StartPhotoEdit(
});
}
void EditCaptionBox::showFinished() {
if (const auto raw = _autocomplete.get()) {
InvokeQueued(raw, [=] {
raw->raise();
});
}
}
void EditCaptionBox::prepare() {
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
@@ -525,6 +534,7 @@ void EditCaptionBox::setupField() {
_field.get(),
Window::GifPauseReason::Layer,
allow);
setupFieldAutocomplete();
Ui::Emoji::SuggestionsController::Init(
getDelegate()->outerContainer(),
_field,
@@ -562,6 +572,55 @@ void EditCaptionBox::setupField() {
});
}
void EditCaptionBox::setupFieldAutocomplete() {
const auto parent = getDelegate()->outerContainer();
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
.parent = parent,
.show = _controller->uiShow(),
.field = _field.get(),
.peer = _historyItem->history()->peer,
.features = [=] {
auto result = ChatHelpers::ComposeFeatures();
result.autocompleteCommands = false;
result.suggestStickersByEmoji = false;
return result;
},
});
const auto raw = _autocomplete.get();
const auto scheduled = std::make_shared<bool>();
const auto recountPostponed = [=] {
if (*scheduled) {
return;
}
*scheduled = true;
Ui::PostponeCall(raw, [=] {
*scheduled = false;
auto field = Ui::MapFrom(parent, this, _field->geometry());
_autocomplete->setBoundings(QRect(
field.x() - _field->x(),
st::defaultBox.margin.top(),
width(),
(field.y()
+ st::defaultComposeFiles.caption.textMargins.top()
+ st::defaultComposeFiles.caption.placeholderShift
+ st::defaultComposeFiles.caption.placeholderFont->height
- st::defaultBox.margin.top())));
});
};
for (auto w = (QWidget*)_field.get(); w; w = w->parentWidget()) {
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
recountPostponed();
}
return base::EventFilterResult::Continue;
});
if (w == parent) {
break;
}
}
}
void EditCaptionBox::setInitialText() {
_field->setTextWithTags(
_initialText,

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace ChatHelpers {
class TabbedPanel;
class FieldAutocomplete;
} // namespace ChatHelpers
namespace Window {
@@ -68,6 +69,8 @@ public:
bool invertCaption,
Fn<void()> saved);
void showFinished() override;
protected:
void prepare() override;
void setInnerFocus() override;
@@ -81,6 +84,7 @@ private:
void setupEditEventHandler();
void setupPhotoEditorEventHandler();
void setupField();
void setupFieldAutocomplete();
void setupControls();
void setInitialText();
@@ -115,6 +119,8 @@ private:
const base::unique_qptr<Ui::InputField> _field;
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
base::unique_qptr<Ui::AbstractSinglePreview> _content;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
base::unique_qptr<QObject> _emojiFilter;

View File

@@ -356,11 +356,12 @@ void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
-> std::unique_ptr<Row> {
if (history->peer->isSelf() || history->peer->isRepliesChat()) {
const auto peer = history->peer;
if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
return nullptr;
} else if (!history->peer->isUser()
&& !history->peer->isChat()
&& !history->peer->isMegagroup()) {
} else if (!peer->isUser()
&& !peer->isChat()
&& !peer->isMegagroup()) {
return nullptr;
}
auto result = std::make_unique<Row>(history);

View File

@@ -131,10 +131,13 @@ ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
}
QString ExceptionRow::generateName() {
return peer()->isSelf()
const auto peer = this->peer();
return peer->isSelf()
? tr::lng_saved_messages(tr::now)
: peer()->isRepliesChat()
: peer->isRepliesChat()
? tr::lng_replies_messages(tr::now)
: peer->isVerifyCodes()
? tr::lng_verification_codes(tr::now)
: Row::generateName();
}
@@ -152,10 +155,11 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
return ForceRoundUserpicCallback(peer);
}
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
using namespace Ui;
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else if (replies) {
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}

View File

@@ -122,9 +122,11 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
top += st.height;
}
for (auto &[history, userpic, name, button] : _removePeer) {
const auto savedMessages = history->peer->isSelf();
const auto repliesMessages = history->peer->isRepliesChat();
if (savedMessages || repliesMessages) {
const auto peer = history->peer;
const auto savedMessages = peer->isSelf();
const auto repliesMessages = peer->isRepliesChat();
const auto verifyCodes = peer->isVerifyCodes();
if (savedMessages || repliesMessages || verifyCodes) {
if (savedMessages) {
Ui::EmptyUserpic::PaintSavedMessages(
p,
@@ -132,13 +134,21 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
top + iconTop,
width(),
st.photoSize);
} else {
} else if (repliesMessages) {
Ui::EmptyUserpic::PaintRepliesMessages(
p,
iconLeft,
top + iconTop,
width(),
st.photoSize);
} else {
history->peer->paintUserpicLeft(
p,
userpic,
iconLeft,
top + iconTop,
width(),
st.photoSize);
}
p.setPen(st::contactsNameFg);
p.drawTextLeft(
@@ -147,7 +157,9 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
width(),
(savedMessages
? tr::lng_saved_messages(tr::now)
: tr::lng_replies_messages(tr::now)));
: repliesMessages
? tr::lng_replies_messages(tr::now)
: tr::lng_verification_codes(tr::now)));
} else {
history->peer->paintUserpicLeft(
p,

View File

@@ -337,12 +337,13 @@ PaintRoundImageCallback ChatRow::generatePaintUserpicCallback(
int y,
int outerWidth,
int size) mutable {
using namespace Ui;
if (forceRound && peer->isForum()) {
ForceRoundUserpicCallback(peer)(p, x, y, outerWidth, size);
} else if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else if (replies) {
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
@@ -582,6 +583,7 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));
@@ -890,6 +892,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
};
const auto getLinkQr = [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_filters_link_qr_about()));

View File

@@ -64,11 +64,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::PremiumSubscriptionOption;
using GiftOptions = Data::PremiumSubscriptionOptions;
[[nodiscard]] QString CreateMessageLink(
not_null<Main::Session*> session,
PeerId peerId,
@@ -87,638 +82,6 @@ using GiftOptions = Data::PremiumSubscriptionOptions;
return QString();
};
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
auto result = GiftOptions();
const auto gifts = data.vpremium_gifts();
if (!gifts) {
return result;
}
result = Api::PremiumSubscriptionOptionsFromTL(gifts->v);
for (auto &option : result) {
option.costPerMonth = tr::lng_premium_gift_per(
tr::now,
lt_cost,
option.costPerMonth);
}
return result;
}
[[nodiscard]] Fn<TextWithEntities(TextWithEntities)> BoostsForGiftText(
const std::vector<not_null<UserData*>> users) {
Expects(!users.empty());
const auto session = &users.front()->session();
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
return [=, count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_reward(
tr::now,
lt_count,
count * BoostsForGift(session),
lt_emoji,
emoji,
Ui::Text::RichLangValue));
return text;
};
}
using TagUser1 = lngtag_user;
using TagUser2 = lngtag_second_user;
using TagUser3 = lngtag_name;
[[nodiscard]] rpl::producer<TextWithEntities> ComplexAboutLabel(
const std::vector<not_null<UserData*>> &users,
tr::phrase<TagUser1> phrase1,
tr::phrase<TagUser1, TagUser2> phrase2,
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
Expects(!users.empty());
const auto count = users.size();
const auto nameValue = [&](not_null<UserData*> user) {
return user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
};
if (count == 1) {
return phrase1(
lt_user,
nameValue(users.front()),
Ui::Text::RichLangValue);
} else if (count == 2) {
return phrase2(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
Ui::Text::RichLangValue);
} else if (count == 3) {
return phrase3(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
} else {
return phraseMore(
lt_count,
rpl::single(count - kUserpicsMax) | tr::to_count(),
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
}
}
[[nodiscard]] not_null<Ui::RpWidget*> CircleBadge(
not_null<Ui::RpWidget*> parent,
const QString &text) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
auto gradient = QLinearGradient(
QPointF(0, full.height()),
QPointF(full.width(), 0));
gradient.setStops(Ui::Premium::GiftGradientStops());
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(full);
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawEllipse(inner);
p.setFont(st::premiumGiftsUserpicBadgeFont);
p.setPen(st::premiumButtonFg);
p.drawText(full, text, style::al_center);
}, widget->lifetime());
widget->resize(full.size());
return widget;
}
[[nodiscard]] not_null<Ui::RpWidget*> UserpicsContainer(
not_null<Ui::RpWidget*> parent,
std::vector<not_null<UserData*>> users) {
Expects(!users.empty());
if (users.size() == 1) {
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
users.front(),
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
return userpic;
}
const auto &singleSize = st::defaultUserpicButton.size;
const auto container = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto single = singleSize.width();
const auto shift = single - st::boostReplaceUserpicsShift;
const auto maxWidth = users.size() * (single - shift) + shift;
container->resize(maxWidth, singleSize.height());
container->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
/ 2;
for (auto i = 0; i < users.size(); i++) {
const auto bg = Ui::CreateChild<Ui::RpWidget>(container);
bg->resize(singleSize);
bg->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(bg->rect());
}, bg->lifetime());
bg->moveToLeft(std::max(0, i * (single - shift)), 0);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
bg,
users[i],
st::premiumGiftsUserpicButton);
userpic->moveToLeft(diff, diff);
}
return container;
}
void GiftBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<UserData*> user,
GiftOptions options) {
const auto boxWidth = st::boxWideWidth;
box->setWidth(boxWidth);
box->setNoContentMargin(true);
const auto buttonsParent = box->verticalLayout().get();
struct State {
rpl::event_stream<QString> buttonText;
};
const auto state = box->lifetime().make_state<State>();
const auto userpicPadding = st::premiumGiftUserpicPadding;
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
buttonsParent,
userpicPadding.top()
+ userpicPadding.bottom()
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
top,
true);
const auto userpic = UserpicsContainer(top, { user });
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue(
) | rpl::start_with_next([=](int width) {
userpic->moveToLeft(
(width - userpic->width()) / 2,
userpicPadding.top());
const auto center = top->rect().center();
const auto size = QSize(
userpic->width() * Ui::Premium::MiniStars::kSizeFactor,
userpic->height());
const auto ministarsRect = QRect(
QPoint(center.x() - size.width(), center.y() - size.height()),
QPoint(center.x() + size.width(), center.y() + size.height()));
stars->setPosition(ministarsRect.topLeft());
stars->setSize(ministarsRect.size());
}, userpic->lifetime());
top->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(top);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, top->lifetime());
const auto close = Ui::CreateChild<Ui::IconButton>(
buttonsParent,
st::infoTopBarClose);
close->setClickedCallback([=] { box->closeBox(); });
buttonsParent->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0, width);
}, close->lifetime());
// Header.
const auto &padding = st::premiumGiftAboutPadding;
const auto available = boxWidth - padding.left() - padding.right();
const auto &stTitle = st::premiumPreviewAboutTitle;
auto titleLabel = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_title(),
stTitle);
titleLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(titleLabel)),
st::premiumGiftTitlePadding);
auto textLabel = Ui::CreateLabelWithCustomEmoji(
box,
tr::lng_premium_gift_about(
lt_user,
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; }),
Ui::Text::RichLangValue
) | rpl::map(
BoostsForGiftText({ user })
),
{ .session = &user->session() },
st::premiumPreviewAbout);
textLabel->setTextColorOverride(stTitle.textFg->c);
textLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(textLabel)),
padding);
// List.
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto groupValueChangedCallback = [=](int value) {
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button(
tr::now,
lt_cost,
options[value].costTotal);
state->buttonText.fire(std::move(text));
};
group->setChangedCallback(groupValueChangedCallback);
Ui::Premium::AddGiftOptions(
buttonsParent,
group,
options,
st::premiumGiftOption);
// Footer.
auto terms = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_terms(
lt_link,
tr::lng_premium_gift_terms_link(
) | rpl::map([=](const QString &t) {
return Ui::Text::Link(t, 1);
}),
Ui::Text::WithEntities),
st::premiumGiftTerms);
terms->setLink(1, std::make_shared<LambdaClickHandler>([=] {
box->closeBox();
Settings::ShowPremium(&user->session(), QString());
}));
terms->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(terms)),
st::premiumGiftTermsPadding);
// Button.
const auto &stButton = st::premiumGiftBox;
box->setStyle(stButton);
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[] { return u"gift"_q; },
state->buttonText.events(),
Ui::Premium::GiftGradientStops(),
[=] {
const auto value = group->current();
return (value < options.size() && value >= 0)
? options[value].botUrl
: QString();
},
});
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
groupValueChangedCallback(0);
Data::PeerPremiumValue(
user
) | rpl::skip(1) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
}
void GiftsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
std::vector<not_null<UserData*>> users,
not_null<Api::PremiumGiftCodeOptions*> api,
const QString &ref) {
Expects(!users.empty());
const auto boxWidth = st::boxWideWidth;
box->setWidth(boxWidth);
box->setNoContentMargin(true);
const auto buttonsParent = box->verticalLayout().get();
const auto session = &users.front()->session();
struct State {
rpl::event_stream<QString> buttonText;
rpl::variable<bool> confirmButtonBusy = false;
rpl::variable<bool> isPaymentComplete = false;
};
const auto state = box->lifetime().make_state<State>();
const auto userpicPadding = st::premiumGiftUserpicPadding;
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
buttonsParent,
userpicPadding.top()
+ userpicPadding.bottom()
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
top,
true);
const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
const auto userpics = UserpicsContainer(
top,
{ users.begin(), users.begin() + maxWithUserpic });
top->widthValue(
) | rpl::start_with_next([=](int width) {
userpics->moveToLeft(
(width - userpics->width()) / 2,
userpicPadding.top());
const auto center = top->rect().center();
const auto size = QSize(
userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
userpics->height());
const auto ministarsRect = QRect(
QPoint(center.x() - size.width(), center.y() - size.height()),
QPoint(center.x() + size.width(), center.y() + size.height()));
stars->setPosition(ministarsRect.topLeft());
stars->setSize(ministarsRect.size());
}, userpics->lifetime());
if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
const auto badge = CircleBadge(
userpics,
QChar('+') + QString::number(rest));
badge->moveToRight(0, userpics->height() - badge->height());
}
top->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(top);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, top->lifetime());
const auto close = Ui::CreateChild<Ui::IconButton>(
buttonsParent,
st::infoTopBarClose);
close->setClickedCallback([=] { box->closeBox(); });
buttonsParent->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0, width);
}, close->lifetime());
// Header.
const auto &padding = st::premiumGiftAboutPadding;
const auto available = boxWidth - padding.left() - padding.right();
const auto &stTitle = st::premiumPreviewAboutTitle;
auto titleLabel = object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->isPaymentComplete.value(),
tr::lng_premium_gifts_about_paid_title(),
tr::lng_premium_gift_title()),
stTitle);
titleLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(titleLabel)),
st::premiumGiftTitlePadding);
// About.
{
auto text = rpl::conditional(
state->isPaymentComplete.value(),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_paid1,
tr::lng_premium_gifts_about_paid2,
tr::lng_premium_gifts_about_paid3,
tr::lng_premium_gifts_about_paid_more
) | rpl::map([count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_paid_below(
tr::now,
lt_count,
float64(count),
Ui::Text::RichLangValue));
return text;
}),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_user1,
tr::lng_premium_gifts_about_user2,
tr::lng_premium_gifts_about_user3,
tr::lng_premium_gifts_about_user_more
) | rpl::map(BoostsForGiftText(users))
);
const auto label = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
Ui::CreateLabelWithCustomEmoji(
box,
std::move(text),
{ .session = session },
st::premiumPreviewAbout)),
padding)->entity();
label->setTextColorOverride(stTitle.textFg->c);
label->resizeToWidth(available);
}
// List.
const auto optionsContainer = buttonsParent->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
buttonsParent,
object_ptr<Ui::VerticalLayout>(buttonsParent)));
const auto options = api->options(users.size());
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto groupValueChangedCallback = [=](int value) {
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button(
tr::now,
lt_cost,
options[value].costTotal);
state->buttonText.fire(std::move(text));
};
group->setChangedCallback(groupValueChangedCallback);
Ui::Premium::AddGiftOptions(
optionsContainer->entity(),
group,
options,
st::premiumGiftOption);
optionsContainer->toggleOn(
state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
anim::type::instant);
// Summary.
{
{
// Will be hidden after payment.
const auto content = optionsContainer->entity();
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
Ui::AddSubsectionTitle(
content,
tr::lng_premium_gifts_summary_subtitle());
}
const auto content = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
{});
auto buttonCallback = [=](PremiumFeature section) {
stars->setPaused(true);
const auto previewBoxShown = [=](
not_null<Ui::BoxContent*> previewBox) {
previewBox->boxClosing(
) | rpl::start_with_next(crl::guard(box, [=] {
stars->setPaused(false);
}), previewBox->lifetime());
};
ShowPremiumPreviewBox(
controller->uiShow(),
section,
previewBoxShown,
true);
};
Settings::AddSummaryPremium(
content,
controller,
ref,
std::move(buttonCallback));
}
// Footer.
{
box->addRow(
object_ptr<Ui::DividerLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gifts_terms(
lt_link,
tr::lng_payments_terms_link(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/tos"_q);
}),
lt_policy,
tr::lng_premium_gifts_terms_policy(
) | rpl::map([](const QString &t) {
using namespace Ui::Text;
return Link(t, u"https://telegram.org/privacy"_q);
}),
Ui::Text::RichLangValue),
st::premiumGiftTerms),
st::defaultBoxDividerLabelPadding),
{});
}
// Button.
const auto &stButton = st::premiumGiftBox;
box->setStyle(stButton);
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[=] { return ref; },
rpl::combine(
state->buttonText.events(),
state->confirmButtonBusy.value(),
state->isPaymentComplete.value()
) | rpl::map([](const QString &text, bool busy, bool paid) {
return busy
? QString()
: paid
? tr::lng_close(tr::now)
: text;
}),
Ui::Premium::GiftGradientStops(),
});
raw->setClickedCallback([=] {
if (state->confirmButtonBusy.current()) {
return;
}
if (state->isPaymentComplete.current()) {
return box->closeBox();
}
auto invoice = api->invoice(
users.size(),
api->monthsFromPreset(group->current()));
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
state->confirmButtonBusy = true;
const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) {
if (const auto strong = weak.data()) {
strong->window()->setFocus();
state->confirmButtonBusy = false;
if (result == Payments::CheckoutResult::Paid) {
state->isPaymentComplete = true;
Ui::StartFireworks(box->parentWidget());
}
}
};
Payments::CheckoutProcess::Start(std::move(invoice), done);
});
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
raw,
raw->height() / 2);
AddChildToWidgetCenter(raw, loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
groupValueChangedCallback(0);
}
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session,
const QString &slug) {
@@ -793,16 +156,55 @@ void GiftsBox(
return result;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeHiddenPeerTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto &st = st::giveawayGiftCodeUserpic;
raw->resize(raw->width(), st.photoSize);
const auto userpic = Ui::CreateChild<Ui::RpWidget>(raw);
const auto usize = st.photoSize;
userpic->resize(usize, usize);
userpic->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(userpic);
Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize);
}, userpic->lifetime());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
tr::lng_gift_from_hidden(),
st::giveawayGiftCodeValue);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto position = st::giveawayGiftCodeNamePosition;
label->resizeToNaturalWidth(width - position.x());
label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width);
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setTextColorOverride(st::windowFg->c);
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
object_ptr<Ui::RpWidget> value,
style::margins valueMargins) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel),
(label
? object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel)
: object_ptr<Ui::FlatLabel>(nullptr)),
std::move(value),
st::giveawayGiftCodeLabelMargin,
valueMargins);
@@ -957,180 +359,6 @@ void ShowAlreadyPremiumToast(
} // namespace
GiftPremiumValidator::GiftPremiumValidator(
not_null<Window::SessionController*> controller)
: _controller(controller)
, _api(&_controller->session().mtp()) {
}
void GiftPremiumValidator::cancel() {
_requestId = 0;
}
void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (_manyGiftsLifetime) {
return;
}
using namespace Api;
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
_controller->session().user());
const auto show = _controller->uiShow();
api->request(
) | rpl::start_with_error_done([=](const QString &error) {
show->showToast(error);
}, [=] {
const auto maxAmount = *ranges::max_element(api->availablePresets());
class Controller final : public ContactsBoxController {
public:
Controller(
not_null<Main::Session*> session,
Fn<bool(int)> checkErrorCallback)
: ContactsBoxController(session)
, _checkErrorCallback(std::move(checkErrorCallback)) {
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
return ContactsBoxController::createRow(user);
}
void rowClicked(not_null<PeerListRow*> row) override {
const auto checked = !row->checked();
if (checked
&& _checkErrorCallback
&& _checkErrorCallback(
delegate()->peerListSelectedRowsCount())) {
return;
}
delegate()->peerListSetRowChecked(row, checked);
}
private:
const Fn<bool(int)> _checkErrorCallback;
};
auto initBox = [=](not_null<PeerListBox*> peersBox) {
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
auto process = [=] {
const auto selected = peersBox->collectSelectedRows();
const auto users = ranges::views::all(
selected
) | ranges::views::transform([](not_null<PeerData*> p) {
return p->asUser();
}) | ranges::views::filter([](UserData *u) -> bool {
return u;
}) | ranges::to<std::vector<not_null<UserData*>>>();
if (users.empty()) {
show->showToast(
tr::lng_settings_gift_premium_choose(tr::now));
return;
}
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
(*ignoreClose) = true;
peersBox->closeBox();
};
peersBox->setTitle(tr::lng_premium_gift_title());
peersBox->addButton(
tr::lng_settings_gift_premium_users_confirm(),
std::move(process));
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
peersBox->boxClosing(
) | rpl::start_with_next([=] {
if (!(*ignoreClose)) {
_manyGiftsLifetime.destroy();
}
}, peersBox->lifetime());
};
auto listController = std::make_unique<Controller>(
&_controller->session(),
[=](int count) {
if (count <= maxAmount) {
return false;
}
show->showToast(tr::lng_settings_gift_premium_users_error(
tr::now,
lt_count,
maxAmount));
return true;
});
show->showBox(
Box<PeerListBox>(
std::move(listController),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}, _manyGiftsLifetime);
}
void GiftPremiumValidator::showChosenPeerBox(
not_null<UserData*> user,
const QString &ref) {
if (_manyGiftsLifetime) {
return;
}
using namespace Api;
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
_controller->session().user());
const auto show = _controller->uiShow();
api->request(
) | rpl::start_with_error_done([=](const QString &error) {
show->showToast(error);
}, [=] {
const auto users = std::vector<not_null<UserData*>>{ user };
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
}, _manyGiftsLifetime);
}
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
if (_requestId) {
return;
}
_requestId = _api.request(MTPusers_GetFullUser(
user->inputUser
)).done([=](const MTPusers_UserFull &result) {
if (!_requestId) {
// Canceled.
return;
}
_requestId = 0;
// _controller->api().processFullPeer(peer, result);
_controller->session().data().processUsers(result.data().vusers());
_controller->session().data().processChats(result.data().vchats());
const auto &fullUser = result.data().vfull_user().data();
auto options = GiftOptionFromTL(fullUser);
if (!options.empty()) {
_controller->show(
Box(GiftBox, _controller, user, std::move(options)));
}
}).fail([=] {
_requestId = 0;
}).send();
}
rpl::producer<QString> GiftDurationValue(int months) {
return GiftDurationPhrase(months)(
lt_count,
@@ -1708,6 +936,77 @@ void ResolveGiveawayInfo(
crl::guard(controller, show));
}
void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
if (peerId) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
controller,
peerId);
} else {
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
MakeHiddenPeerTableValue(table, controller),
st::giveawayGiftCodePeerMargin);
}
if (!entry.date.isNull()) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
}
if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{
QString::number(entry.limitedCount)
});
AddTableRow(
table,
tr::lng_gift_availability(),
((entry.limitedLeft > 0)
? tr::lng_gift_availability_left(
lt_count,
rpl::single(entry.limitedLeft * 1.),
lt_amount,
std::move(amount),
Ui::Text::WithEntities)
: tr::lng_gift_availability_none(
lt_amount,
std::move(amount),
Ui::Text::WithEntities)));
}
if (!entry.description.empty()) {
const auto session = &controller->session();
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(update),
};
};
auto label = object_ptr<Ui::FlatLabel>(
table,
rpl::single(entry.description),
st::giveawayGiftMessage,
st::defaultPopupMenu,
makeContext);
label->setSelectable(true);
table->addRow(
nullptr,
std::move(label),
st::giveawayGiftCodeLabelMargin,
st::giveawayGiftCodeValueMargin);
}
}
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,

View File

@@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/sender.h"
class UserData;
namespace Api {
struct GiftCode;
} // namespace Api
@@ -29,29 +27,9 @@ class VerticalLayout;
} // namespace Ui
namespace Window {
class SessionController;
class SessionNavigation;
} // namespace Window
class GiftPremiumValidator final {
public:
GiftPremiumValidator(not_null<Window::SessionController*> controller);
void showBox(not_null<UserData*> user);
void showChoosePeerBox(const QString &ref);
void showChosenPeerBox(not_null<UserData*> user, const QString &ref);
void cancel();
private:
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
mtpRequestId _requestId = 0;
rpl::lifetime _manyGiftsLifetime;
};
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
[[nodiscard]] QString GiftDuration(int months);
@@ -76,6 +54,10 @@ void ResolveGiveawayInfo(
std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results);
void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry);
void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container,

View File

@@ -447,6 +447,8 @@ void PeerListBox::addSelectItem(
? tr::lng_saved_short(tr::now)
: (respect && peer->isRepliesChat())
? tr::lng_replies_messages(tr::now)
: (respect && peer->isVerifyCodes())
? tr::lng_verification_codes(tr::now)
: peer->shortName();
addSelectItem(
peer->id.value,
@@ -625,6 +627,8 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
? tr::lng_saved_messages(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
: _isVerifyCodesChat
? tr::lng_verification_codes(tr::now)
: generateName();
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
}
@@ -695,6 +699,8 @@ QString PeerListRow::generateShortName() {
? tr::lng_saved_short(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
: _isVerifyCodesChat
? tr::lng_verification_codes(tr::now)
: peer()->shortName();
}
@@ -715,10 +721,11 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
return ForceRoundUserpicCallback(peer);
}
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
using namespace Ui;
if (saved) {
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
} else if (replies) {
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
} else {
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
}
@@ -757,12 +764,14 @@ int PeerListRow::paintNameIconGetWidth(
int availableWidth,
int outerWidth,
bool selected) {
if (special()
if (_skipPeerBadge
|| special()
|| !_savedMessagesStatus.isEmpty()
|| _isRepliesMessagesChat) {
|| _isRepliesMessagesChat
|| _isVerifyCodesChat) {
return 0;
}
return _bagde.drawGetWidth(
return _badge.drawGetWidth(
p,
QRect(
nameLeft,
@@ -874,12 +883,13 @@ void PeerListRow::paintDisabledCheckUserpic(
auto iconBorderPen = st.checkbox.check.border->p;
iconBorderPen.setWidth(st.checkbox.selectWidth);
const auto size = userpicRadius * 2;
if (!_savedMessagesStatus.isEmpty()) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, size);
} else if (_isRepliesMessagesChat) {
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, size);
} else {
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, size);
}
{
@@ -1069,10 +1079,13 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
if (!savedMessagesStatus.isEmpty() && !row->special()) {
if (row->peer()->isSelf()) {
const auto peer = row->peer();
if (peer->isSelf()) {
row->setSavedMessagesChatStatus(savedMessagesStatus);
} else if (row->peer()->isRepliesChat()) {
} else if (peer->isRepliesChat()) {
row->setIsRepliesMessagesChat(true);
} else if (peer->isVerifyCodes()) {
row->setIsVerifyCodesChat(true);
}
}
_rowsById.emplace(row->id(), row);

View File

@@ -200,6 +200,9 @@ public:
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
_isRepliesMessagesChat = isRepliesMessagesChat;
}
void setIsVerifyCodesChat(bool isVerifyCodesChat) {
_isVerifyCodesChat = isVerifyCodesChat;
}
template <typename UpdateCallback>
void setChecked(
@@ -251,6 +254,10 @@ public:
return _nameFirstLetters;
}
void setSkipPeerBadge(bool skip) {
_skipPeerBadge = skip;
}
virtual void lazyInitialize(const style::PeerListItem &st);
virtual void paintStatusText(
Painter &p,
@@ -288,7 +295,7 @@ private:
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
Ui::Text::String _name;
Ui::Text::String _status;
Ui::PeerBadge _bagde;
Ui::PeerBadge _badge;
StatusType _statusType = StatusType::Online;
crl::time _statusValidTill = 0;
base::flat_set<QChar> _nameFirstLetters;
@@ -299,6 +306,8 @@ private:
bool _initialized : 1 = false;
bool _isSearchResult : 1 = false;
bool _isRepliesMessagesChat : 1 = false;
bool _isVerifyCodesChat : 1 = false;
bool _skipPeerBadge : 1 = false;
};

View File

@@ -842,6 +842,7 @@ auto ChooseRecipientBoxController::createRow(
? !_filter(history)
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| peer->isRepliesChat()
|| peer->isVerifyCodes()
|| (peer->isUser() && (_premiumRequiredError
? !peer->asUser()->canSendIgnoreRequirePremium()
: !Data::CanSendAnything(peer))));

View File

@@ -269,7 +269,8 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
const auto peer = this->peer();
const auto saved = peer->isSelf();
const auto replies = peer->isRepliesChat();
auto userpic = (saved || replies)
const auto verifyCodes = peer->isVerifyCodes();
auto userpic = (saved || replies || verifyCodes)
? Ui::PeerUserpicView()
: ensureUserpicView();
auto paint = [=](
@@ -302,6 +303,7 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
repaint = (_paletteVersion != style::PaletteVersion())
|| (!saved
&& !replies
&& !verifyCodes
&& (_userpicKey != peer->userpicUniqueKey(userpic)));
}
if (repaint) {

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/components/credits.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
@@ -1591,6 +1592,9 @@ void Controller::fillBotBalanceButton() {
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>();
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
state->balance = QString::number(balance);
}
const auto wrap = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
@@ -1604,7 +1608,7 @@ void Controller::fillBotBalanceButton() {
},
st::manageGroupButton,
{})));
wrap->toggle(false, anim::type::instant);
wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
const auto button = wrap->entity();
{

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/boxes/edit_invite_link_session.h"
#include "ui/boxes/peer_qr_box.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h"
#include "ui/controls/userpic_button.h"
@@ -64,8 +65,8 @@ namespace {
constexpr auto kFirstPage = 20;
constexpr auto kPerPage = 100;
constexpr auto kShareQrSize = 768;
constexpr auto kShareQrPadding = 16;
// constexpr auto kShareQrSize = 768;
// constexpr auto kShareQrPadding = 16;
using LinkData = Api::InviteLink;
@@ -282,6 +283,8 @@ private:
return updated.link.isEmpty() || (!revoked && updated.revoked);
}
#if 0
QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
const auto image = [](int size) {
auto result = QImage(
@@ -383,6 +386,8 @@ void QrBox(
box->addLeftButton(tr::lng_group_invite_context_copy(), copyCallback);
}
#endif
Controller::Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
@@ -421,6 +426,7 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
});
const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
_peer,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@@ -1253,6 +1259,7 @@ void AddPermanentLinkBlock(
const auto getLinkQr = crl::guard(weak, [=] {
if (const auto current = value->current(); !current.link.isEmpty()) {
show->showBox(InviteLinkQrBox(
peer,
current.link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));
@@ -1510,16 +1517,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
}
object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about) {
return Box(QrBox, link, std::move(title), std::move(about), [=](
const QImage &image,
std::shared_ptr<Ui::Show> show) {
auto mime = std::make_unique<QMimeData>();
mime->setImageData(image);
QGuiApplication::clipboard()->setMimeData(mime.release());
show->showToast(tr::lng_group_invite_qr_copied(tr::now));
return Box([=, t = std::move(title), a = std::move(about)](
not_null<Ui::GenericBox*> box) {
Ui::FillPeerQrBox(box, peer, link, std::move(a));
box->setTitle(std::move(t));
});
}

View File

@@ -50,6 +50,7 @@ void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
const QString &link,
const QString &copied = {});
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
PeerData *peer,
const QString &link,
rpl::producer<QString> title,
rpl::producer<QString> about);

View File

@@ -587,6 +587,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
}, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
nullptr,
link,
tr::lng_group_invite_qr_title(),
tr::lng_group_invite_qr_about()));

View File

@@ -1129,11 +1129,14 @@ void ShowEditPeerPermissionsBox(
disabledByAdminRights,
tr::lng_rights_permission_cant_edit(tr::now));
if (const auto channel = peer->asChannel()) {
if (channel->isPublic()
|| (channel->isMegagroup() && channel->linkedChat())) {
if (channel->isPublic()) {
result.emplace(
Flag::ChangeInfo | Flag::PinMessages,
tr::lng_rights_permission_unavailable(tr::now));
} else if (channel->isMegagroup() && channel->linkedChat()) {
result.emplace(
Flag::ChangeInfo | Flag::PinMessages,
tr::lng_rights_permission_in_discuss(tr::now));
}
}
return result;

View File

@@ -8,23 +8,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/report_messages_box.h"
#include "api/api_report.h"
#include "core/application.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "lang/lang_keys.h"
#include "ui/boxes/report_box.h"
#include "ui/boxes/report_box_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
namespace {
[[nodiscard]] object_ptr<Ui::BoxContent> Report(
not_null<PeerData*> peer,
std::variant<
v::null_t,
MessageIdsList,
not_null<PhotoData*>,
StoryId> data,
std::variant<v::null_t, not_null<PhotoData*>> data,
const style::ReportBox *stOverride) {
const auto source = v::match(data, [](const MessageIdsList &ids) {
return Ui::ReportSource::Message;
@@ -62,64 +67,151 @@ namespace {
} // namespace
object_ptr<Ui::BoxContent> ReportItemsBox(
not_null<PeerData*> peer,
MessageIdsList ids) {
return Report(peer, ids, nullptr);
}
object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
not_null<PeerData*> peer,
not_null<PhotoData*> photo) {
return Report(peer, photo, nullptr);
}
void ShowReportPeerBox(
not_null<Window::SessionController*> window,
not_null<PeerData*> peer) {
struct State {
QPointer<Ui::BoxContent> reasonBox;
QPointer<Ui::BoxContent> detailsBox;
MessageIdsList ids;
};
const auto state = std::make_shared<State>();
const auto chosen = [=](Ui::ReportReason reason) {
const auto send = [=](const QString &text) {
window->clearChooseReportMessages();
Api::SendReport(
window->uiShow(),
peer,
reason,
text,
std::move(state->ids));
if (const auto strong = state->reasonBox.data()) {
strong->closeBox();
void ShowReportMessageBox(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
const std::vector<MsgId> &ids,
const std::vector<StoryId> &stories,
const style::ReportBox *stOverride) {
const auto report = Api::CreateReportMessagesOrStoriesCallback(
show,
peer);
auto performRequest = [=](
const auto &repeatRequest,
Data::ReportInput reportInput) -> void {
constexpr auto kToastDuration = crl::time(4000);
report(reportInput, [=](const Api::ReportResult &result) {
if (!result.error.isEmpty()) {
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
const auto widget = show->toastParent();
const auto window = Core::App().findWindow(widget);
const auto controller = window
? window->sessionController()
: nullptr;
if (controller) {
const auto callback = [=](std::vector<MsgId> ids) {
auto copy = reportInput;
copy.ids = std::move(ids);
repeatRequest(repeatRequest, std::move(copy));
};
controller->showChooseReportMessages(
peer,
reportInput,
std::move(callback));
}
} else {
show->showToast(result.error);
}
return;
}
if (const auto strong = state->detailsBox.data()) {
strong->closeBox();
if (!result.options.empty() || result.commentOption) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(
rpl::single(
result.title.isEmpty()
? reportInput.optionText
: result.title));
for (const auto &option : result.options) {
const auto button = Ui::AddReportOptionButton(
box->verticalLayout(),
option.text,
stOverride);
button->setClickedCallback([=] {
auto copy = reportInput;
copy.optionId = option.id;
copy.optionText = option.text;
repeatRequest(repeatRequest, std::move(copy));
});
}
if (const auto commentOption = result.commentOption) {
constexpr auto kReportReasonLengthMax = 512;
const auto &st = stOverride
? stOverride
: &st::defaultReportBox;
Ui::AddReportDetailsIconButton(box);
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto details = box->addRow(
object_ptr<Ui::InputField>(
box,
st->field,
Ui::InputField::Mode::MultiLine,
commentOption->optional
? tr::lng_report_details_optional()
: tr::lng_report_details_non_optional(),
QString()));
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
{
const auto container = box->verticalLayout();
auto label = object_ptr<Ui::FlatLabel>(
container,
tr::lng_report_details_message_about(),
st::boxDividerLabel);
label->setTextColorOverride(st->dividerFg->c);
using namespace Ui;
const auto widget = container->add(
object_ptr<PaddingWrap<>>(
container,
std::move(label),
st::defaultBoxDividerLabelPadding));
const auto background
= CreateChild<BoxContentDivider>(
widget,
st::boxDividerHeight,
st->dividerBg,
RectPart::Top | RectPart::Bottom);
background->lower();
widget->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
background->resize(s);
}, background->lifetime());
}
details->setMaxLength(kReportReasonLengthMax);
box->setFocusCallback([=] {
details->setFocusFast();
});
const auto submit = [=] {
if (!commentOption->optional
&& details->empty()) {
details->showError();
details->setFocus();
return;
}
auto copy = reportInput;
copy.optionId = commentOption->id;
copy.comment = details->getLastText();
repeatRequest(repeatRequest, std::move(copy));
};
details->submits(
) | rpl::start_with_next(submit, details->lifetime());
box->addButton(tr::lng_report_button(), submit);
} else {
box->addButton(
tr::lng_close(),
[=] { show->hideLayer(); });
}
if (!reportInput.optionId.isNull()) {
box->addLeftButton(
tr::lng_create_group_back(),
[=] { box->closeBox(); });
}
}));
} else if (result.successful) {
show->showToast(
tr::lng_report_thanks(tr::now),
kToastDuration);
show->hideLayer();
}
};
if (reason == Ui::ReportReason::Fake
|| reason == Ui::ReportReason::Other) {
state->ids = {};
state->detailsBox = window->show(
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
return;
}
window->showChooseReportMessages(peer, reason, [=](
MessageIdsList ids) {
state->ids = std::move(ids);
state->detailsBox = window->show(
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
});
};
state->reasonBox = window->show(Box(
Ui::ReportReasonBox,
st::defaultReportBox,
(peer->isBroadcast()
? Ui::ReportSource::Channel
: peer->isUser()
? Ui::ReportSource::Bot
: Ui::ReportSource::Group),
chosen));
performRequest(performRequest, { .ids = ids, .stories = stories });
}

View File

@@ -12,20 +12,22 @@ class object_ptr;
namespace Ui {
class BoxContent;
class Show;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Main
namespace style {
struct ReportBox;
} // namespace style
class PeerData;
[[nodiscard]] object_ptr<Ui::BoxContent> ReportItemsBox(
not_null<PeerData*> peer,
MessageIdsList ids);
[[nodiscard]] object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
not_null<PeerData*> peer,
not_null<PhotoData*> photo);
void ShowReportPeerBox(
not_null<Window::SessionController*> window,
not_null<PeerData*> peer);
void ShowReportMessageBox(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer,
const std::vector<MsgId> &ids,
const std::vector<StoryId> &stories,
const style::ReportBox *stOverride = nullptr);

View File

@@ -272,10 +272,13 @@ void SendCreditsBox(
state->confirmButtonBusy = true;
session->api().request(
MTPpayments_SendStarsForm(
MTP_flags(0),
MTP_long(form->formId),
form->inputInvoice)
).done([=](auto result) {
).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
if (weak) {
state->confirmButtonBusy = false;
box->closeBox();
@@ -311,41 +314,22 @@ void SendCreditsBox(
AddChildToWidgetCenter(button.data(), loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
{
auto buttonText = tr::lng_credits_box_out_confirm(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_emoji,
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue);
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::single(QString()),
st::creditsBoxButtonLabel);
std::move(
buttonText
) | rpl::start_with_next([=](const TextWithEntities &text) {
buttonLabel->setMarkedText(
text,
Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [=] { buttonLabel->update(); },
});
}, buttonLabel->lifetime());
buttonLabel->setTextColorOverride(
box->getDelegate()->style().button.textFg->c);
button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
buttonLabel->moveToLeft(
(size.width() - buttonLabel->width()) / 2,
(size.height() - buttonLabel->height()) / 2);
}, buttonLabel->lifetime());
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
state->confirmButtonBusy.value(
) | rpl::start_with_next([=](bool busy) {
buttonLabel->setVisible(!busy);
}, buttonLabel->lifetime());
}
SetButtonMarkedLabel(
button,
rpl::combine(
tr::lng_credits_box_out_confirm(
lt_count,
rpl::single(form->invoice.amount) | tr::to_count(),
lt_emoji,
rpl::single(CreditsEmojiSmall(session)),
Ui::Text::RichLangValue),
state->confirmButtonBusy.value()
) | rpl::map([](TextWithEntities &&text, bool busy) {
return busy ? TextWithEntities() : std::move(text);
}),
session,
st::creditsBoxButtonLabel,
box->getDelegate()->style().button.textFg->c);
const auto buttonWidth = st::boxWidth
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
@@ -405,4 +389,73 @@ TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
QString(QChar(0x2B50)));
}
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context,
const style::FlatLabel &st,
std::optional<QColor> textFg) {
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::single(QString()),
st);
rpl::duplicate(
text
) | rpl::filter([=](const TextWithEntities &text) {
return !text.text.isEmpty();
}) | rpl::start_with_next([=](const TextWithEntities &text) {
buttonLabel->setMarkedText(
text,
context([=] { buttonLabel->update(); }));
}, buttonLabel->lifetime());
if (textFg) {
buttonLabel->setTextColorOverride(textFg);
}
button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
buttonLabel->moveToLeft(
(size.width() - buttonLabel->width()) / 2,
(size.height() - buttonLabel->height()) / 2);
}, buttonLabel->lifetime());
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
buttonLabel->showOn(std::move(
text
) | rpl::map([=](const TextWithEntities &text) {
return !text.text.isEmpty();
}));
return buttonLabel;
}
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
not_null<Main::Session*> session,
const style::FlatLabel &st,
std::optional<QColor> textFg) {
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = update,
};
}, st, textFg);
}
void SendStarGift(
not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done) {
session->api().request(MTPpayments_SendStarsForm(
MTP_long(data->formId),
data->inputInvoice
)).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates());
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
done(std::nullopt);
}).fail([=](const MTP::Error &error) {
done(error.type());
}).send();
}
} // namespace Ui

View File

@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem;
namespace style {
struct FlatLabel;
} // namespace style
namespace Main {
class Session;
} // namespace Main
@@ -19,7 +23,9 @@ struct CreditsFormData;
namespace Ui {
class RpWidget;
class GenericBox;
class FlatLabel;
void SendCreditsBox(
not_null<Ui::GenericBox*> box,
@@ -32,4 +38,23 @@ void SendCreditsBox(
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
not_null<Main::Session*> session);
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
Fn<std::any(Fn<void()> update)> context,
const style::FlatLabel &st,
std::optional<QColor> textFg = {});
not_null<FlatLabel*> SetButtonMarkedLabel(
not_null<RpWidget*> button,
rpl::producer<TextWithEntities> text,
not_null<Main::Session*> session,
const style::FlatLabel &st,
std::optional<QColor> textFg = {});
void SendStarGift(
not_null<Main::Session*> session,
std::shared_ptr<Payments::CreditsFormData> data,
Fn<void(std::optional<QString>)> done);
} // namespace Ui

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h"
#include "menu/menu_send.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/field_autocomplete.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "editor/photo_editor_layer_widget.h"
@@ -32,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h"
#include "platform/platform_file_utilities.h"
#include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h"
#include "ui/widgets/checkbox.h"
@@ -72,7 +72,7 @@ constexpr auto kMaxMessageLength = 4096;
using Ui::SendFilesWay;
[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
return !urls.isEmpty() && ranges::all_of(urls, Core::UrlIsLocal);
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
}
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
@@ -1267,13 +1267,17 @@ void SendFilesBox::setupCaption() {
: (_limits & SendFilesAllow::EmojiWithoutPremium);
};
const auto show = _show;
InitMessageFieldHandlers(
&show->session(),
show,
_caption.data(),
[=] { return show->paused(Window::GifPauseReason::Layer); },
allow,
&_st.files.caption);
InitMessageFieldHandlers({
.session = &show->session(),
.show = show,
.field = _caption.data(),
.customEmojiPaused = [=] {
return show->paused(Window::GifPauseReason::Layer);
},
.allowPremiumEmoji = allow,
.fieldStyle = &_st.files.caption,
});
setupCaptionAutocomplete();
Ui::Emoji::SuggestionsController::Init(
getDelegate()->outerContainer(),
_caption,
@@ -1333,6 +1337,59 @@ void SendFilesBox::setupCaption() {
}, _caption->lifetime());
}
void SendFilesBox::setupCaptionAutocomplete() {
if (!_captionToPeer || !_caption) {
return;
}
const auto parent = getDelegate()->outerContainer();
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
.parent = parent,
.show = _show,
.field = _caption.data(),
.peer = _captionToPeer,
.features = [=] {
auto result = ChatHelpers::ComposeFeatures();
result.autocompleteCommands = false;
result.suggestStickersByEmoji = false;
return result;
},
.sendMenuDetails = _sendMenuDetails,
});
const auto raw = _autocomplete.get();
const auto scheduled = std::make_shared<bool>();
const auto recountPostponed = [=] {
if (*scheduled) {
return;
}
*scheduled = true;
Ui::PostponeCall(raw, [=] {
*scheduled = false;
auto field = Ui::MapFrom(parent, this, _caption->geometry());
_autocomplete->setBoundings(QRect(
field.x() - _caption->x(),
st::defaultBox.margin.top(),
width(),
(field.y()
+ _st.files.caption.textMargins.top()
+ _st.files.caption.placeholderShift
+ _st.files.caption.placeholderFont->height
- st::defaultBox.margin.top())));
});
};
for (auto w = (QWidget*)_caption.data(); w; w = w->parentWidget()) {
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
recountPostponed();
}
return base::EventFilterResult::Continue;
});
if (w == parent) {
break;
}
}
}
void SendFilesBox::checkCharsLimitation() {
const auto limits = Data::PremiumLimits(&_show->session());
const auto caption = (_caption && !_caption->isHidden())
@@ -1648,6 +1705,14 @@ void SendFilesBox::updateControlsGeometry() {
_scroll->move(0, _titleHeight.current());
}
void SendFilesBox::showFinished() {
if (const auto raw = _autocomplete.get()) {
InvokeQueued(raw, [=] {
raw->raise();
});
}
}
void SendFilesBox::setInnerFocus() {
if (_caption && !_caption->isHidden()) {
_caption->setFocusFast();

View File

@@ -28,6 +28,7 @@ enum class SendType;
namespace ChatHelpers {
class TabbedPanel;
class Show;
class FieldAutocomplete;
} // namespace ChatHelpers
namespace Ui {
@@ -126,6 +127,8 @@ public:
_cancelledCallback = std::move(callback);
}
void showFinished() override;
~SendFilesBox();
protected:
@@ -206,6 +209,7 @@ private:
void refreshControls(bool initial = false);
void setupSendWayControls();
void setupCaption();
void setupCaptionAutocomplete();
void setupEmojiPanel();
void updateSendWayControls();
@@ -257,6 +261,7 @@ private:
SendFilesLimits _limits = {};
Fn<MenuDetails()> _sendMenuDetails;
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
PeerData *_captionToPeer = nullptr;
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
@@ -268,6 +273,7 @@ private:
bool _invertCaption = false;
object_ptr<Ui::InputField> _caption = { nullptr };
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
TextWithTags _prefilledCaptionText;
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;

View File

@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/send_gif_with_caption_box.h"
#include "base/event_filter.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/field_autocomplete.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
@@ -30,9 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/controls/emoji_button_factory.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
@@ -226,6 +229,7 @@ namespace {
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
not_null<PeerData*> peer,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done) {
const auto window = Core::App().findWindow(box);
@@ -255,6 +259,61 @@ void SendGifWithCaptionBox(
return true;
});
const auto sendMenuDetails = [=] { return details; };
struct Autocomplete {
std::unique_ptr<ChatHelpers::FieldAutocomplete> dropdown;
bool geometryUpdateScheduled = false;
};
const auto autocomplete = box->lifetime().make_state<Autocomplete>();
const auto outer = box->getDelegate()->outerContainer();
ChatHelpers::InitFieldAutocomplete(autocomplete->dropdown, {
.parent = outer,
.show = controller->uiShow(),
.field = input,
.peer = peer,
.features = [=] {
auto result = ChatHelpers::ComposeFeatures();
result.autocompleteCommands = false;
result.suggestStickersByEmoji = false;
return result;
},
.sendMenuDetails = sendMenuDetails,
});
const auto raw = autocomplete->dropdown.get();
const auto recountPostponed = [=] {
if (autocomplete->geometryUpdateScheduled) {
return;
}
autocomplete->geometryUpdateScheduled = true;
Ui::PostponeCall(raw, [=] {
autocomplete->geometryUpdateScheduled = false;
const auto from = input->parentWidget();
auto field = Ui::MapFrom(outer, from, input->geometry());
const auto &st = st::defaultComposeFiles;
autocomplete->dropdown->setBoundings(QRect(
field.x() - input->x(),
st::defaultBox.margin.top(),
input->width(),
(field.y()
+ st.caption.textMargins.top()
+ st.caption.placeholderShift
+ st.caption.placeholderFont->height
- st::defaultBox.margin.top())));
});
};
for (auto w = (QWidget*)input; w; w = w->parentWidget()) {
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
recountPostponed();
}
return base::EventFilterResult::Continue;
});
if (w == outer) {
break;
}
}
const auto send = [=](Api::SendOptions options) {
done(std::move(options), input->getTextWithTags());
};
@@ -264,8 +323,15 @@ void SendGifWithCaptionBox(
SendMenu::SetupMenuAndShortcuts(
confirm,
controller->uiShow(),
[=] { return details; },
sendMenuDetails,
SendMenu::DefaultCallback(controller->uiShow(), send));
box->setShowFinishedCallback([=] {
if (const auto raw = autocomplete->dropdown.get()) {
InvokeQueued(raw, [=] {
raw->raise();
});
}
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class PeerData;
class DocumentData;
namespace Api {
@@ -24,6 +25,7 @@ class GenericBox;
void SendGifWithCaptionBox(
not_null<Ui::GenericBox*> box,
not_null<DocumentData*> document,
not_null<PeerData*> peer,
const SendMenu::Details &details,
Fn<void(Api::SendOptions, TextWithTags)> done);

View File

@@ -240,13 +240,12 @@ void ShareBox::prepareCommentField() {
}, field->lifetime());
if (const auto show = uiShow(); show->valid()) {
InitMessageFieldHandlers(
_descriptor.session,
Main::MakeSessionShow(show, _descriptor.session),
field,
nullptr,
nullptr,
_descriptor.stLabel);
InitMessageFieldHandlers({
.session = _descriptor.session,
.show = Main::MakeSessionShow(show, _descriptor.session),
.field = field,
.fieldStyle = _descriptor.stLabel,
});
}
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
@@ -838,6 +837,8 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
? tr::lng_saved_messages(tr::now)
: peer->isRepliesChat()
? tr::lng_replies_messages(tr::now)
: peer->isVerifyCodes()
? tr::lng_verification_codes(tr::now)
: peer->name();
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
/*
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 Window {
class SessionController;
} // namespace Window
namespace Ui {
void ChooseStarGiftRecipient(
not_null<Window::SessionController*> controller);
void ShowStarGiftBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
} // namespace Ui

View File

@@ -82,7 +82,8 @@ using TLStickerSet = MTPmessages_StickerSet;
[[nodiscard]] std::optional<QColor> ComputeImageColor(
const style::icon &lockIcon,
const QImage &frame) {
const QImage &frame,
RectPart part) {
if (frame.isNull()
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
return {};
@@ -91,13 +92,29 @@ using TLStickerSet = MTPmessages_StickerSet;
auto sg = int64();
auto sb = int64();
auto sa = int64();
const auto factor = frame.devicePixelRatio();
const auto factor = style::DevicePixelRatio();
const auto size = lockIcon.size() * factor;
const auto width = std::min(frame.width(), size.width());
const auto height = std::min(frame.height(), size.height());
const auto skipx = (frame.width() - width) / 2;
const auto radius = st::roundRadiusSmall;
const auto skipy = std::max(frame.height() - height - radius, 0);
const auto skipx = (part == RectPart::TopLeft
|| part == RectPart::Left
|| part == RectPart::BottomLeft)
? 0
: (part == RectPart::Top
|| part == RectPart::Center
|| part == RectPart::Bottom)
? (frame.width() - width) / 2
: std::max(frame.width() - width - radius, 0);
const auto skipy = (part == RectPart::TopLeft
|| part == RectPart::Top
|| part == RectPart::TopRight)
? 0
: (part == RectPart::Left
|| part == RectPart::Center
|| part == RectPart::Right)
? (frame.height() - height) / 2
: std::max(frame.height() - height - radius, 0);
const auto perline = frame.bytesPerLine();
const auto addperline = perline - (width * 4);
auto bits = static_cast<const uchar*>(frame.bits())
@@ -121,17 +138,20 @@ using TLStickerSet = MTPmessages_StickerSet;
[[nodiscard]] QColor ComputeLockColor(
const style::icon &lockIcon,
const QImage &frame) {
const QImage &frame,
RectPart part) {
return ComputeImageColor(
lockIcon,
frame
frame,
part
).value_or(st::windowSubTextFg->c);
}
void ValidatePremiumLockBg(
const style::icon &lockIcon,
QImage &image,
const QImage &frame) {
const QImage &frame,
RectPart part) {
if (!image.isNull()) {
return;
}
@@ -142,7 +162,7 @@ void ValidatePremiumLockBg(
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(factor);
auto p = QPainter(&image);
const auto color = ComputeLockColor(lockIcon, frame);
const auto color = ComputeLockColor(lockIcon, frame, part);
p.fillRect(
QRect(QPoint(), size),
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
@@ -195,8 +215,10 @@ void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
StickerPremiumMark::StickerPremiumMark(
not_null<Main::Session*> session,
const style::icon &lockIcon)
: _lockIcon(lockIcon) {
const style::icon &lockIcon,
RectPart part)
: _lockIcon(lockIcon)
, _part(part) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_lockGray = QImage();
@@ -221,11 +243,15 @@ void StickerPremiumMark::paint(
const auto &bg = frame.isNull() ? _lockGray : backCache;
const auto factor = style::DevicePixelRatio();
const auto radius = st::roundRadiusSmall;
const auto point = position + QPoint(
(singleSize.width() - (bg.width() / factor) - radius),
singleSize.height() - (bg.height() / factor) - radius);
const auto shiftx = (_part == RectPart::Center)
? (singleSize.width() - (bg.width() / factor)) / 2
: (singleSize.width() - (bg.width() / factor) - radius);
const auto shifty = (_part == RectPart::Center)
? (singleSize.height() - (bg.height() / factor)) / 2
: (singleSize.height() - (bg.height() / factor) - radius);
const auto point = position + QPoint(shiftx, shifty);
p.drawImage(point, bg);
if (_premium) {
if (_premium && _part != RectPart::Center) {
validateStar();
p.drawImage(point, _star);
} else {
@@ -237,7 +263,7 @@ void StickerPremiumMark::validateLock(
const QImage &frame,
QImage &backCache) {
auto &image = frame.isNull() ? _lockGray : backCache;
ValidatePremiumLockBg(_lockIcon, image, frame);
ValidatePremiumLockBg(_lockIcon, image, frame, _part);
}
void StickerPremiumMark::validateStar() {
@@ -1402,9 +1428,13 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
const auto send = crl::guard(this, [=](Api::SendOptions options) {
chosen(index, document, options);
});
// In case we're adding items after FillSendMenu we have
// to pass nullptr for showForEffect and attach selector later.
// Otherwise added items widths won't be respected in menu geometry.
SendMenu::FillSendMenu(
_menu.get(),
_show,
nullptr, // showForEffect
details,
SendMenu::DefaultCallback(_show, send));
@@ -1438,6 +1468,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
.isAttention = true,
});
}
SendMenu::AttachSendMenuEffect(
_menu.get(),
_show,
details,
SendMenu::DefaultCallback(_show, send));
}
if (_menu->empty()) {
_menu = nullptr;

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/box_content.h"
#include "base/timer.h"
#include "data/stickers/data_stickers.h"
#include "ui/rect_part.h"
namespace Window {
class SessionController;
@@ -32,7 +33,8 @@ class StickerPremiumMark final {
public:
StickerPremiumMark(
not_null<Main::Session*> session,
const style::icon &lockIcon);
const style::icon &lockIcon,
RectPart part = RectPart::Bottom);
void paint(
QPainter &p,
@@ -49,6 +51,7 @@ private:
const style::icon &_lockIcon;
QImage _lockGray;
QImage _star;
RectPart _part = RectPart::Bottom;
bool _premium = false;
rpl::lifetime _lifetime;

View File

@@ -1139,6 +1139,7 @@ groupCallTitle: WindowTitle(defaultWindowTitle) {
bgActive: groupCallBg;
fg: transparent;
fgActive: transparent;
oneSideControls: true;
minimize: IconButton(groupCallTitleButton) {
icon: groupCallTitleMinimizeIcon;
iconOver: groupCallTitleMinimizeIconOver;

View File

@@ -198,7 +198,9 @@ void Panel::initWindow() {
return Flag::None | Flag(0);
}
#ifndef Q_OS_MAC
if (_controls->controls.geometry().contains(widgetPoint)) {
using Result = Ui::Platform::HitTestResult;
const auto windowPoint = widget()->mapTo(window(), widgetPoint);
if (_controls->controls.hitTest(windowPoint) != Result::None) {
return Flag::None | Flag(0);
}
#endif // !Q_OS_MAC

View File

@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <tgcalls/desktop_capturer/DesktopCaptureSourceManager.h>
#include <tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h>
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
namespace Calls::Group::Ui::DesktopCapture {
@@ -585,13 +584,7 @@ void ChooseSourceProcess::setupSourcesGeometry() {
void ChooseSourceProcess::setupGeometryWithParent(
not_null<QWidget*> parent) {
const auto parentScreen = [&] {
if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) {
return screen;
}
return parent->screen();
}();
const auto parentScreen = parent->screen();
const auto myScreen = _window->screen();
if (parentScreen && myScreen != parentScreen) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)

View File

@@ -218,8 +218,11 @@ ComposeControls {
ReportBox {
button: SettingsButton;
noIconButton: SettingsButton;
label: FlatLabel;
field: InputField;
dividerBg: color;
dividerFg: color;
spam: icon;
fake: icon;
violence: icon;
@@ -1360,8 +1363,13 @@ reportReasonButton: SettingsButton(defaultSettingsButton) {
defaultReportBox: ReportBox {
button: reportReasonButton;
noIconButton: SettingsButton(reportReasonButton) {
padding: margins(22px, 7px, 8px, 7px);
}
label: boxLabel;
field: newGroupDescription;
dividerBg: boxDividerBg;
dividerFg: windowSubTextFg;
spam: menuIconDelete;
fake: menuIconFake;
violence: menuIconViolence;

View File

@@ -10,20 +10,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace ChatHelpers {
struct ComposeFeatures {
bool likes = false;
bool sendAs = true;
bool ttlInfo = true;
bool botCommandSend = true;
bool silentBroadcastToggle = true;
bool attachBotsMenu = true;
bool inlineBots = true;
bool megagroupSet = true;
bool stickersSettings = true;
bool openStickerSets = true;
bool autocompleteHashtags = true;
bool autocompleteMentions = true;
bool autocompleteCommands = true;
bool commonTabbedPanel = true;
bool likes : 1 = false;
bool sendAs : 1 = true;
bool ttlInfo : 1 = true;
bool botCommandSend : 1 = true;
bool silentBroadcastToggle : 1 = true;
bool attachBotsMenu : 1 = true;
bool inlineBots : 1 = true;
bool megagroupSet : 1 = true;
bool stickersSettings : 1 = true;
bool openStickerSets : 1 = true;
bool autocompleteHashtags : 1 = true;
bool autocompleteMentions : 1 = true;
bool autocompleteCommands : 1 = true;
bool suggestStickersByEmoji : 1 = true;
bool commonTabbedPanel : 1 = true;
};
} // namespace ChatHelpers

View File

@@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
const auto kSets = {
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
Set{ { 1, 1804, 8'115'639, "Android" }, PreviewPath(1) },
Set{ { 2, 1805, 5'481'197, "Twemoji" }, PreviewPath(2) },
Set{ { 3, 1806, 7'047'594, "JoyPixels" }, PreviewPath(3) },
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) },
};
using Loading = MTP::DedicatedLoader::Progress;

View File

@@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
}
SuggestionsController::SuggestionsController(
not_null<QWidget*> parent,
not_null<QWidget*> outer,
not_null<QTextEdit*> field,
not_null<Main::Session*> session,
const Options &options)
: _st(options.st ? *options.st : st::defaultEmojiSuggestions)
: QObject(parent)
, _st(options.st ? *options.st : st::defaultEmojiSuggestions)
, _field(field)
, _session(session)
, _showExactTimer([=] { showWithQuery(getEmojiQuery()); })

View File

@@ -37,7 +37,7 @@ class SuggestionsWidget;
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
class SuggestionsController {
class SuggestionsController final : public QObject {
public:
struct Options {
bool suggestExactFirstWord = true;
@@ -47,6 +47,7 @@ public:
};
SuggestionsController(
not_null<QWidget*> parent,
not_null<QWidget*> outer,
not_null<QTextEdit*> field,
not_null<Main::Session*> session,

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/business/data_shortcut_messages.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication>
namespace ChatHelpers {
namespace {
[[nodiscard]] QString PrimaryUsername(not_null<UserData*> user) {
@@ -60,6 +62,18 @@ namespace {
return usernames.empty() ? user->username() : usernames.front();
}
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last)
; i != e
; ++i) {
if (i->user == elem) {
return (i - b);
}
}
return -1;
}
} // namespace
class FieldAutocomplete::Inner final : public Ui::RpWidget {
@@ -70,7 +84,7 @@ public:
};
Inner(
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Show> show,
const style::EmojiPan &st,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
@@ -127,7 +141,7 @@ private:
Media::Clip::Notification notification,
not_null<DocumentData*> document);
const std::shared_ptr<ChatHelpers::Show> _show;
const std::shared_ptr<Show> _show;
const not_null<Main::Session*> _session;
const style::EmojiPan &_st;
const not_null<FieldAutocomplete*> _parent;
@@ -191,13 +205,7 @@ struct FieldAutocomplete::BotCommandRow {
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller)
: FieldAutocomplete(parent, controller->uiShow()) {
}
FieldAutocomplete::FieldAutocomplete(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Show> show,
const style::EmojiPan *stOverride)
: RpWidget(parent)
, _show(std::move(show))
@@ -235,10 +243,26 @@ FieldAutocomplete::FieldAutocomplete(
}), lifetime());
}
std::shared_ptr<ChatHelpers::Show> FieldAutocomplete::uiShow() const {
std::shared_ptr<Show> FieldAutocomplete::uiShow() const {
return _show;
}
void FieldAutocomplete::requestRefresh() {
_refreshRequests.fire({});
}
rpl::producer<> FieldAutocomplete::refreshRequests() const {
return _refreshRequests.events();
}
void FieldAutocomplete::requestStickersUpdate() {
_stickersUpdateRequests.fire({});
}
rpl::producer<> FieldAutocomplete::stickersUpdateRequests() const {
return _stickersUpdateRequests.events();
}
auto FieldAutocomplete::mentionChosen() const
-> rpl::producer<FieldAutocomplete::MentionChosen> {
return _inner->mentionChosen();
@@ -365,6 +389,10 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
updateFiltered(resetScroll);
}
EmojiPtr FieldAutocomplete::stickersEmoji() const {
return _emoji;
}
bool FieldAutocomplete::clearFilteredBotCommands() {
if (_brows.empty()) {
return false;
@@ -373,18 +401,6 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
return true;
}
namespace {
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
if (i->user == elem) {
return (i - b);
}
}
return -1;
}
}
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
const auto data = &_session->data().stickers();
const auto list = data->getListByEmoji({ _emoji }, _stickersSeed);
@@ -871,7 +887,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
}
FieldAutocomplete::Inner::Inner(
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Show> show,
const style::EmojiPan &st,
not_null<FieldAutocomplete*> parent,
not_null<MentionRows*> mrows,
@@ -963,8 +979,8 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
media->checkStickerSmall();
const auto paused = _show->paused(
ChatHelpers::PauseReason::TabbedPanel);
const auto size = ChatHelpers::ComputeStickerSize(
PauseReason::TabbedPanel);
const auto size = ComputeStickerSize(
document,
stickerBoundingBox());
const auto ppos = pos + QPoint(
@@ -989,7 +1005,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
} else if (const auto image = media->getStickerSmall()) {
p.drawPixmapLeft(ppos, width(), image->pix(size));
} else {
ChatHelpers::PaintStickerThumbnailPath(
PaintStickerThumbnailPath(
p,
media.get(),
QRect(ppos, size),
@@ -1250,7 +1266,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
const auto bounding = selectedRect(index);
auto contentRect = QRect(
QPoint(),
ChatHelpers::ComputeStickerSize(
ComputeStickerSize(
document,
stickerBoundingBox()));
contentRect.moveCenter(bounding.center());
@@ -1464,9 +1480,9 @@ auto FieldAutocomplete::Inner::getLottieRenderer()
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
const auto document = suggestion.document;
suggestion.lottie = ChatHelpers::LottiePlayerFromDocument(
suggestion.lottie = LottiePlayerFromDocument(
suggestion.documentMedia.get(),
ChatHelpers::StickerLottieSize::InlineResults,
StickerLottieSize::InlineResults,
stickerBoundingBox() * style::DevicePixelRatio(),
Lottie::Quality::Default,
getLottieRenderer());
@@ -1534,7 +1550,7 @@ void FieldAutocomplete::Inner::clipCallback(
} else if (i->webm->state() == State::Error) {
i->webm.setBad();
} else if (i->webm->ready() && !i->webm->started()) {
const auto size = ChatHelpers::ComputeStickerSize(
const auto size = ComputeStickerSize(
i->document,
stickerBoundingBox());
i->webm->start({ .frame = size, .keepAlpha = true });
@@ -1632,3 +1648,171 @@ auto FieldAutocomplete::Inner::scrollToRequested() const
-> rpl::producer<ScrollTo> {
return _scrollToRequested.events();
}
void InitFieldAutocomplete(
std::unique_ptr<FieldAutocomplete> &autocomplete,
FieldAutocompleteDescriptor &&descriptor) {
Expects(!autocomplete);
autocomplete = std::make_unique<FieldAutocomplete>(
descriptor.parent,
descriptor.show,
descriptor.stOverride);
const auto raw = autocomplete.get();
const auto field = descriptor.field;
field->rawTextEdit()->installEventFilter(raw);
field->customTab(true);
raw->mentionChosen(
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
const auto user = data.user;
if (data.mention.isEmpty()) {
field->insertTag(
user->firstName.isEmpty() ? user->name() : user->firstName,
PrepareMentionTag(user));
} else {
field->insertTag('@' + data.mention);
}
}, raw->lifetime());
const auto sendCommand = descriptor.sendBotCommand;
const auto setText = descriptor.setText;
raw->hashtagChosen(
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
field->insertTag(data.hashtag);
}, raw->lifetime());
const auto peer = descriptor.peer;
const auto features = descriptor.features;
const auto processShortcut = descriptor.processShortcut;
const auto shortcutMessages = (processShortcut != nullptr)
? &peer->owner().shortcutMessages()
: nullptr;
raw->botCommandChosen(
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
if (!features().autocompleteCommands) {
return;
}
using Method = FieldAutocompleteChooseMethod;
const auto byTab = (data.method == Method::ByTab);
const auto shortcut = data.user->isSelf();
// Send bot command at once, if it was not inserted by pressing Tab.
if (byTab && data.command.size() > 1) {
field->insertTag(data.command);
} else if (!shortcut) {
sendCommand(data.command);
setText(
field->getTextWithTagsPart(field->textCursor().position()));
} else if (processShortcut) {
processShortcut(data.command.mid(1));
}
}, raw->lifetime());
raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback));
if (const auto stickerChoosing = descriptor.stickerChoosing) {
raw->choosingProcesses(
) | rpl::start_with_next([=](FieldAutocomplete::Type type) {
if (type == FieldAutocomplete::Type::Stickers) {
stickerChoosing();
}
}, raw->lifetime());
}
if (const auto chosen = descriptor.stickerChosen) {
raw->stickerChosen(
) | rpl::start_with_next(chosen, raw->lifetime());
}
field->tabbed(
) | rpl::start_with_next([=] {
if (!raw->isHidden()) {
raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
}
}, raw->lifetime());
const auto check = [=] {
auto parsed = ParseMentionHashtagBotCommandQuery(field, features());
if (parsed.query.isEmpty()) {
} else if (parsed.query[0] == '#'
&& cRecentWriteHashtags().isEmpty()
&& cRecentSearchHashtags().isEmpty()) {
peer->session().local().readRecentHashtagsAndBots();
} else if (parsed.query[0] == '@'
&& cRecentInlineBots().isEmpty()) {
peer->session().local().readRecentHashtagsAndBots();
} else if (parsed.query[0] == '/'
&& peer->isUser()
&& !peer->asUser()->isBot()
&& (!shortcutMessages
|| shortcutMessages->shortcuts().list.empty())) {
parsed = {};
}
raw->showFiltered(peer, parsed.query, parsed.fromStart);
};
const auto updateStickersByEmoji = [=] {
const auto errorForStickers = Data::RestrictionError(
peer,
ChatRestriction::SendStickers);
if (features().suggestStickersByEmoji && !errorForStickers) {
const auto &text = field->getTextWithTags().text;
auto length = 0;
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
if (text.size() <= length) {
raw->showStickers(emoji);
return;
}
}
}
raw->showStickers(nullptr);
};
raw->refreshRequests(
) | rpl::start_with_next(check, raw->lifetime());
raw->stickersUpdateRequests(
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
peer->owner().botCommandsChanges(
) | rpl::filter([=](not_null<PeerData*> changed) {
return (peer == changed);
}) | rpl::start_with_next([=] {
if (raw->clearFilteredBotCommands()) {
check();
}
}, raw->lifetime());
peer->owner().stickers().updated(
Data::StickersType::Stickers
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
QObject::connect(
field->rawTextEdit(),
&QTextEdit::cursorPositionChanged,
raw,
check,
Qt::QueuedConnection);
field->changes() | rpl::start_with_next(
updateStickersByEmoji,
raw->lifetime());
peer->session().changes().peerUpdates(
Data::PeerUpdate::Flag::Rights
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer == peer);
}) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
if (shortcutMessages) {
shortcutMessages->shortcutsChanged(
) | rpl::start_with_next(check, raw->lifetime());
}
raw->setSendMenuDetails(std::move(descriptor.sendMenuDetails));
raw->hideFast();
}
} // namespace ChatHelpers

View File

@@ -46,46 +46,49 @@ struct Details;
} // namespace SendMenu
namespace ChatHelpers {
struct ComposeFeatures;
struct FileChosen;
class Show;
} // namespace ChatHelpers
enum class FieldAutocompleteChooseMethod {
ByEnter,
ByTab,
ByClick,
};
class FieldAutocomplete final : public Ui::RpWidget {
public:
FieldAutocomplete(
QWidget *parent,
not_null<Window::SessionController*> controller);
FieldAutocomplete(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Show> show,
const style::EmojiPan *stOverride = nullptr);
~FieldAutocomplete();
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
[[nodiscard]] std::shared_ptr<Show> uiShow() const;
bool clearFilteredBotCommands();
void showFiltered(
not_null<PeerData*> peer,
QString query,
bool addInlineBots);
void showStickers(EmojiPtr emoji);
[[nodiscard]] EmojiPtr stickersEmoji() const;
void setBoundings(QRect boundings);
const QString &filter() const;
ChatData *chat() const;
ChannelData *channel() const;
UserData *user() const;
[[nodiscard]] const QString &filter() const;
[[nodiscard]] ChatData *chat() const;
[[nodiscard]] ChannelData *channel() const;
[[nodiscard]] UserData *user() const;
int32 innerTop();
int32 innerBottom();
[[nodiscard]] int32 innerTop();
[[nodiscard]] int32 innerBottom();
bool eventFilter(QObject *obj, QEvent *e) override;
enum class ChooseMethod {
ByEnter,
ByTab,
ByClick,
};
using ChooseMethod = FieldAutocompleteChooseMethod;
struct MentionChosen {
not_null<UserData*> user;
QString mention;
@@ -100,7 +103,7 @@ public:
QString command;
ChooseMethod method = ChooseMethod::ByEnter;
};
using StickerChosen = ChatHelpers::FileChosen;
using StickerChosen = FileChosen;
enum class Type {
Mentions,
Hashtags,
@@ -110,13 +113,14 @@ public:
bool chooseSelected(ChooseMethod method) const;
bool stickersShown() const {
[[nodiscard]] bool stickersShown() const {
return !_srows.empty();
}
bool overlaps(const QRect &globalRect) {
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
[[nodiscard]] bool overlaps(const QRect &globalRect) {
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) {
return false;
}
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
}
@@ -129,11 +133,16 @@ public:
void showAnimated();
void hideAnimated();
rpl::producer<MentionChosen> mentionChosen() const;
rpl::producer<HashtagChosen> hashtagChosen() const;
rpl::producer<BotCommandChosen> botCommandChosen() const;
rpl::producer<StickerChosen> stickerChosen() const;
rpl::producer<Type> choosingProcesses() const;
void requestRefresh();
[[nodiscard]] rpl::producer<> refreshRequests() const;
void requestStickersUpdate();
[[nodiscard]] rpl::producer<> stickersUpdateRequests() const;
[[nodiscard]] rpl::producer<MentionChosen> mentionChosen() const;
[[nodiscard]] rpl::producer<HashtagChosen> hashtagChosen() const;
[[nodiscard]] rpl::producer<BotCommandChosen> botCommandChosen() const;
[[nodiscard]] rpl::producer<StickerChosen> stickerChosen() const;
[[nodiscard]] rpl::producer<Type> choosingProcesses() const;
protected:
void paintEvent(QPaintEvent *e) override;
@@ -157,7 +166,7 @@ private:
void recount(bool resetScroll = false);
StickerRows getStickerSuggestions();
const std::shared_ptr<ChatHelpers::Show> _show;
const std::shared_ptr<Show> _show;
const not_null<Main::Session*> _session;
const style::EmojiPan &_st;
QPixmap _cache;
@@ -189,7 +198,30 @@ private:
bool _hiding = false;
Ui::Animations::Simple _a_opacity;
rpl::event_stream<> _refreshRequests;
rpl::event_stream<> _stickersUpdateRequests;
Fn<bool(int)> _moderateKeyActivateCallback;
};
struct FieldAutocompleteDescriptor {
not_null<QWidget*> parent;
std::shared_ptr<Show> show;
not_null<Ui::InputField*> field;
const style::EmojiPan *stOverride = nullptr;
not_null<PeerData*> peer;
Fn<ComposeFeatures()> features;
Fn<SendMenu::Details()> sendMenuDetails;
Fn<void()> stickerChoosing;
Fn<void(FileChosen&&)> stickerChosen;
Fn<void(TextWithTags)> setText;
Fn<void(QString)> sendBotCommand;
Fn<void(QString)> processShortcut;
Fn<bool(int)> moderateKeyActivateCallback;
};
void InitFieldAutocomplete(
std::unique_ptr<FieldAutocomplete> &autocomplete,
FieldAutocompleteDescriptor &&descriptor);
} // namespace ChatHelpers

View File

@@ -402,24 +402,30 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
// inline results don't have effects
copyDetails.effectAllowed = false;
}
// In case we're adding items after FillSendMenu we have
// to pass nullptr for showForEffect and attach selector later.
// Otherwise added items widths won't be respected in menu geometry.
SendMenu::FillSendMenu(
menu,
_show,
nullptr, // showForMenu
copyDetails,
SendMenu::DefaultCallback(_show, send),
icons);
if (!isInlineResult) {
if (!isInlineResult && _inlineQueryPeer) {
auto done = crl::guard(this, [=](
Api::SendOptions options,
TextWithTags text) {
selectInlineResult(selected, options, true, std::move(text));
});
const auto show = _show;
const auto peer = _inlineQueryPeer;
menu->addAction(tr::lng_send_gif_with_caption(tr::now), [=] {
show->show(Box(
Ui::SendGifWithCaptionBox,
item->getDocument(),
peer,
copyDetails,
std::move(done)));
}, &st::menuIconEdit);
@@ -439,6 +445,13 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
AddGifAction(std::move(callback), _show, document, icons);
}
}
SendMenu::AttachSendMenuEffect(
menu,
_show,
copyDetails,
SendMenu::DefaultCallback(_show, send));
return menu;
}

View File

@@ -63,6 +63,12 @@ constexpr auto kParseLinksTimeout = crl::time(1000);
constexpr auto kTypesDuration = 4 * crl::time(1000);
constexpr auto kCodeLanguageLimit = 32;
constexpr auto kLinkProtocols = {
"http://",
"https://",
"tonsite://"
};
// For mention / custom emoji tags save and validate selfId,
// ignore tags for different users.
[[nodiscard]] Fn<QString(QStringView)> FieldTagMimeProcessor(
@@ -147,13 +153,23 @@ void EditLinkBox(
object_ptr<Ui::RpWidget>(content),
st::markdownLinkFieldPadding);
placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto link = [&] {
if (!startLink.trimmed().isEmpty()) {
return startLink.trimmed();
}
const auto clipboard = QGuiApplication::clipboard()->text().trimmed();
const auto starts = [&](const auto &protocol) {
return clipboard.startsWith(protocol);
};
return std::ranges::any_of(kLinkProtocols, starts) ? clipboard : QString();
}();
const auto url = Ui::AttachParentChild(
content,
object_ptr<Ui::InputField>(
content,
fieldSt,
tr::lng_formatting_link_url(),
startLink.trimmed()));
link));
url->heightValue(
) | rpl::start_with_next([placeholder](int height) {
placeholder->resize(placeholder->width(), height);
@@ -209,6 +225,9 @@ void EditLinkBox(
if (startText.isEmpty()) {
text->setFocusFast();
} else {
if (!url->empty()) {
url->selectAll();
}
url->setFocusFast();
}
});
@@ -216,12 +235,31 @@ void EditLinkBox(
url->customTab(true);
text->customTab(true);
const auto clearFullSelection = [=](not_null<Ui::InputField*> input) {
if (input->empty()) {
return;
}
auto cursor = input->rawTextEdit()->textCursor();
const auto hasFull = (!cursor.selectionStart()
&& (cursor.selectionEnd()
== (input->rawTextEdit()->document()->characterCount() - 1)));
if (hasFull) {
cursor.clearSelection();
input->setTextCursor(cursor);
}
};
url->tabbed(
) | rpl::start_with_next([=] {
clearFullSelection(url);
text->setFocus();
}, url->lifetime());
text->tabbed(
) | rpl::start_with_next([=] {
if (!url->empty()) {
url->selectAll();
}
clearFullSelection(text);
url->setFocus();
}, text->lifetime());
}
@@ -385,18 +423,14 @@ Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
};
}
void InitMessageFieldHandlers(
not_null<Main::Session*> session,
std::shared_ptr<Main::SessionShow> show,
not_null<Ui::InputField*> field,
Fn<bool()> customEmojiPaused,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji,
const style::InputField *fieldStyle) {
const auto paused = [customEmojiPaused] {
return customEmojiPaused && customEmojiPaused();
void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
const auto paused = [passed = args.customEmojiPaused] {
return passed && passed();
};
const auto field = args.field;
const auto session = args.session;
field->setTagMimeProcessor(
FieldTagMimeProcessor(session, allowPremiumEmoji));
FieldTagMimeProcessor(session, args.allowPremiumEmoji));
field->setCustomTextContext([=](Fn<void()> repaint) {
return std::any(Core::MarkedTextContext{
.session = session,
@@ -410,12 +444,14 @@ void InitMessageFieldHandlers(
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
field->setMarkdownReplacesEnabled(true);
if (show) {
field->setMarkdownReplacesEnabled(rpl::single(Ui::MarkdownEnabledState{
Ui::MarkdownEnabled{ std::move(args.allowMarkdownTags) }
}));
if (const auto &show = args.show) {
field->setEditLinkCallback(
DefaultEditLinkCallback(show, field, fieldStyle));
DefaultEditLinkCallback(show, field, args.fieldStyle));
field->setEditLanguageCallback(DefaultEditLanguageCallback(show));
InitSpellchecker(show, field, fieldStyle != nullptr);
InitSpellchecker(show, field, args.fieldStyle != nullptr);
}
const auto style = field->lifetime().make_state<Ui::ChatStyle>(
session->colorIndicesValue());
@@ -515,12 +551,15 @@ void InitMessageFieldHandlers(
not_null<Ui::InputField*> field,
ChatHelpers::PauseReason pauseReasonLevel,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji) {
InitMessageFieldHandlers(
&controller->session(),
controller->uiShow(),
field,
[=] { return controller->isGifPausedAtLeastFor(pauseReasonLevel); },
allowPremiumEmoji);
InitMessageFieldHandlers({
.session = &controller->session(),
.show = controller->uiShow(),
.field = field,
.customEmojiPaused = [=] {
return controller->isGifPausedAtLeastFor(pauseReasonLevel);
},
.allowPremiumEmoji = std::move(allowPremiumEmoji),
});
}
void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
@@ -536,14 +575,16 @@ void InitMessageField(
std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::InputField*> field,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji) {
InitMessageFieldHandlers(
&show->session(),
show,
field,
[=] { return show->paused(ChatHelpers::PauseReason::Any); },
std::move(allowPremiumEmoji));
InitMessageFieldHandlers({
.session = &show->session(),
.show = show,
.field = field,
.customEmojiPaused = [=] {
return show->paused(ChatHelpers::PauseReason::Any);
},
.allowPremiumEmoji = std::move(allowPremiumEmoji),
});
InitMessageFieldGeometry(field);
field->customTab(true);
}
void InitMessageField(

View File

@@ -54,13 +54,18 @@ Fn<bool(
const style::InputField *fieldStyle = nullptr);
Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
std::shared_ptr<Ui::Show> show);
void InitMessageFieldHandlers(
not_null<Main::Session*> session,
std::shared_ptr<Main::SessionShow> show, // may be null
not_null<Ui::InputField*> field,
Fn<bool()> customEmojiPaused,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji = nullptr,
const style::InputField *fieldStyle = nullptr);
struct MessageFieldHandlersArgs {
not_null<Main::Session*> session;
std::shared_ptr<Main::SessionShow> show; // may be null
not_null<Ui::InputField*> field;
Fn<bool()> customEmojiPaused;
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji;
const style::InputField *fieldStyle = nullptr;
base::flat_set<QString> allowMarkdownTags;
};
void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args);
void InitMessageFieldHandlers(
not_null<Window::SessionController*> controller,
not_null<Ui::InputField*> field,

View File

@@ -22,6 +22,10 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
GiftBoxPack::~GiftBoxPack() = default;
rpl::producer<> GiftBoxPack::updated() const {
return _updated.events();
}
int GiftBoxPack::monthsForStars(int stars) const {
if (stars <= 1000) {
return 3;
@@ -112,6 +116,7 @@ void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
}
});
}
_updated.fire({});
}
} // namespace Stickers

View File

@@ -28,6 +28,7 @@ public:
[[nodiscard]] int monthsForStars(int stars) const;
[[nodiscard]] DocumentData *lookup(int months) const;
[[nodiscard]] Data::FileOrigin origin() const;
[[nodiscard]] rpl::producer<> updated() const;
private:
using SetId = uint64;
@@ -36,6 +37,7 @@ private:
const not_null<Main::Session*> _session;
const std::vector<int> _localMonths;
rpl::event_stream<> _updated;
std::vector<DocumentData*> _documents;
SetId _setId = 0;
uint64 _accessHash = 0;

View File

@@ -1780,9 +1780,13 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
});
});
const auto icons = &st().icons;
// In case we're adding items after FillSendMenu we have
// to pass nullptr for showForEffect and attach selector later.
// Otherwise added items widths won't be respected in menu geometry.
SendMenu::FillSendMenu(
menu,
_show,
nullptr, // showForEffect
details,
SendMenu::DefaultCallback(_show, send),
icons);
@@ -1816,6 +1820,13 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
false);
}, &icons->menuRecentRemove);
}
SendMenu::AttachSendMenuEffect(
menu,
_show,
details,
SendMenu::DefaultCallback(_show, send));
return menu;
}

View File

@@ -1162,7 +1162,14 @@ bool Application::openCustomUrl(
|| passcodeLocked()) {
return false;
}
const auto command = base::StringViewMid(urlTrimmed, protocol.size(), 8192);
static const auto kTagExp = QRegularExpression(
u"^\\~[a-zA-Z0-9_\\-]+\\~:"_q);
auto skip = protocol.size();
const auto match = kTagExp.match(base::StringViewMid(urlTrimmed, skip));
if (match.hasMatch()) {
skip += match.capturedLength();
}
const auto command = base::StringViewMid(urlTrimmed, skip, 8192);
const auto my = context.value<ClickHandlerContext>();
const auto controller = my.sessionWindow.get()
? my.sessionWindow.get()

View File

@@ -367,17 +367,6 @@ void MonospaceClickHandler::onClick(ClickContext context) const {
}
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
auto &data = controller->session().data();
const auto item = data.message(my.itemId);
const auto hasCopyRestriction = item
&& (!item->history()->peer->allowsForwarding()
|| item->forbidsForward());
if (hasCopyRestriction) {
controller->showToast(item->history()->peer->isBroadcast()
? tr::lng_error_nocopy_channel(tr::now)
: tr::lng_error_nocopy_group(tr::now));
return;
}
controller->showToast(tr::lng_text_copied(tr::now));
}
TextUtilities::SetClipboardText(TextForMimeData::Simple(_text.trimmed()));

View File

@@ -221,7 +221,8 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(noWarningExtensions)
+ Serialize::stringSize(_customFontFamily)
+ sizeof(qint32) * 3
+ Serialize::bytearraySize(_tonsiteStorageToken);
+ Serialize::bytearraySize(_tonsiteStorageToken)
+ sizeof(qint32);
auto result = QByteArray();
result.reserve(size);
@@ -375,7 +376,8 @@ QByteArray Settings::serialize() const {
1000000))
<< qint32(_systemUnlockEnabled ? 1 : 0)
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
<< _tonsiteStorageToken;
<< _tonsiteStorageToken
<< qint32(_includeMutedCounterFolders ? 1 : 0);
}
Ensures(result.size() == size);
@@ -423,6 +425,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 sendFilesWay = _sendFilesWay.serialize();
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay.current());
qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0;
qint32 includeMutedCounterFolders = _includeMutedCounterFolders ? 1 : 0;
qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0;
std::optional<QString> noWarningExtensions;
qint32 legacyExeLaunchWarning = 1;
@@ -804,6 +807,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> tonsiteStorageToken;
}
if (!stream.atEnd()) {
stream >> includeMutedCounterFolders;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -844,6 +850,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
case ScreenCorner::BottomLeft: _notificationsCorner = uncheckedNotificationsCorner; break;
}
_includeMutedCounter = (includeMutedCounter == 1);
_includeMutedCounterFolders = (includeMutedCounterFolders == 1);
_countUnreadMessages = (countUnreadMessages == 1);
_notifyAboutPinned = (notifyAboutPinned == 1);
_autoLock = autoLock;
@@ -864,8 +871,6 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
case Ui::InputSubmitSettings::Enter:
case Ui::InputSubmitSettings::CtrlEnter: _sendSubmitWay = uncheckedSendSubmitWay; break;
}
_includeMutedCounter = (includeMutedCounter == 1);
_countUnreadMessages = (countUnreadMessages == 1);
if (noWarningExtensions) {
const auto list = noWarningExtensions->mid(0, 10240)
.split(' ', Qt::SkipEmptyParts)
@@ -1345,6 +1350,7 @@ void Settings::resetOnLastLogout() {
//_notificationsCount = 3;
//_notificationsCorner = ScreenCorner::BottomRight;
_includeMutedCounter = true;
_includeMutedCounterFolders = true;
_countUnreadMessages = true;
_notifyAboutPinned = true;
//_autoLock = 3600;

View File

@@ -238,6 +238,12 @@ public:
void setIncludeMutedCounter(bool value) {
_includeMutedCounter = value;
}
[[nodiscard]] bool includeMutedCounterFolders() const {
return _includeMutedCounterFolders;
}
void setIncludeMutedCounterFolders(bool value) {
_includeMutedCounterFolders = value;
}
[[nodiscard]] bool countUnreadMessages() const {
return _countUnreadMessages;
}
@@ -951,6 +957,7 @@ private:
int _notificationsCount = 3;
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
bool _includeMutedCounter = true;
bool _includeMutedCounterFolders = true;
bool _countUnreadMessages = true;
rpl::variable<bool> _notifyAboutPinned = true;
int _autoLock = 3600;

View File

@@ -7,10 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Core {
bool UrlIsLocal(const QUrl &url);
} // namespace Core
namespace Main {
class Session;
} // namespace Main
@@ -49,7 +45,7 @@ void ShowInFolder(const QString &filepath);
namespace internal {
inline QString UrlToLocalDefault(const QUrl &url) {
return Core::UrlIsLocal(url) ? url.toLocalFile() : QString();
return url.toLocalFile();
}
void UnsafeOpenUrlDefault(const QString &url);

View File

@@ -27,10 +27,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_non_panel_process.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/edit_privacy_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/star_gift_box.h"
#include "boxes/language_box.h"
#include "passport/passport_form_controller.h"
#include "ui/text/text_utilities.h"
@@ -783,9 +785,9 @@ bool CopyPeerId(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
TextUtilities::SetClipboardText({ match->captured(1) });
if (controller) {
controller->showToast(tr::lng_text_copied(tr::now));
controller->showToast(u"ID copied to clipboard."_q);
}
return true;
}
@@ -924,6 +926,34 @@ bool ShowCollectibleUsername(
return true;
}
bool CopyUsernameLink(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto username = match->captured(1);
TextUtilities::SetClipboardText({
controller->session().createInternalLinkFull(username)
});
controller->showToast(tr::lng_username_copied(tr::now));
return true;
}
bool CopyUsername(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto username = match->captured(1);
TextUtilities::SetClipboardText({ '@' + username });
controller->showToast(tr::lng_username_text_copied(tr::now));
return true;
}
bool ShowStarsExamples(
Window::SessionController *controller,
const Match &match,
@@ -1131,10 +1161,7 @@ bool ResolvePremiumMultigift(
if (!controller) {
return false;
}
const auto params = url_parse_params(
match->captured(1).mid(1),
qthelp::UrlParamNameTransform::ToLower);
controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q));
Ui::ChooseStarGiftRecipient(controller);
controller->window().activate();
return true;
}
@@ -1392,6 +1419,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
ShowCollectibleUsername,
},
{
u"^username_link/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
CopyUsernameLink,
},
{
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
CopyUsername,
},
{
u"^stars_examples$"_q,
ShowStarsExamples,

View File

@@ -226,24 +226,13 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
if (data->hasImage()) {
return true;
} else if (const auto urls = ReadMimeUrls(data); !urls.empty()) {
if (ranges::all_of(urls, UrlIsLocal)) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
}
return false;
}
bool UrlIsLocal(const QUrl &url) {
if (!url.isLocalFile()) {
return false;
}
const auto result = url.toLocalFile();
if (result.startsWith("//")) {
return false;
}
return !result.isEmpty();
}
QString FileExtension(const QString &filepath) {
const auto reversed = ranges::views::reverse(filepath);
const auto last = ranges::find_first_of(reversed, ".\\/");

View File

@@ -68,7 +68,6 @@ struct MimeImageData {
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
[[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> data);
[[nodiscard]] bool UrlIsLocal(const QUrl &url);
enum class NameType : uchar {
Unknown,

View File

@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/sandbox.h"
#include "core/click_handler_types.h"
#include "data/components/sponsored_messages.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_session.h"
#include "iv/iv_instance.h"
@@ -296,16 +295,6 @@ Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
return my ? my->customEmojiRepaint : nullptr;
}
bool UiIntegration::allowClickHandlerActivation(
const std::shared_ptr<ClickHandler> &handler,
const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto window = my.sessionWindow.get()) {
window->session().sponsoredMessages().clicked(my.itemId);
}
return true;
}
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
return Core::App().passcodeLockChanges() | rpl::to_empty;
}

View File

@@ -61,9 +61,6 @@ public:
QStringView data,
const std::any &context) override;
Fn<void()> createSpoilerRepaint(const std::any &context) override;
bool allowClickHandlerActivation(
const std::shared_ptr<ClickHandler> &handler,
const ClickContext &context) override;
QString phraseContextCopyText() override;
QString phraseContextCopyEmail() override;

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 = 5005002;
constexpr auto AppVersionStr = "5.5.2";
constexpr auto AppVersion = 5006002;
constexpr auto AppVersionStr = "5.6.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -69,6 +69,11 @@ uint64 Credits::balance() const {
return _nonLockedBalance.current();
}
uint64 Credits::balance(PeerId peerId) const {
const auto it = _cachedPeerBalances.find(peerId);
return (it != _cachedPeerBalances.end()) ? it->second : 0;
}
rpl::producer<uint64> Credits::balanceValue() const {
return _nonLockedBalance.value();
}
@@ -119,4 +124,8 @@ void Credits::apply(uint64 balance) {
}
}
void Credits::apply(PeerId peerId, uint64 balance) {
_cachedPeerBalances[peerId] = balance;
}
} // namespace Data

View File

@@ -24,11 +24,13 @@ public:
void load(bool force = false);
void apply(uint64 balance);
void apply(PeerId peerId, uint64 balance);
[[nodiscard]] bool loaded() const;
[[nodiscard]] rpl::producer<bool> loadedValue() const;
[[nodiscard]] uint64 balance() const;
[[nodiscard]] uint64 balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
[[nodiscard]] rpl::producer<float64> rateValue(
not_null<PeerData*> ownedBotOrChannel);
@@ -47,6 +49,8 @@ private:
std::unique_ptr<Api::CreditsStatus> _loader;
base::flat_map<PeerId, uint64> _cachedPeerBalances;
uint64 _balance = 0;
uint64 _locked = 0;
rpl::variable<uint64> _nonLockedBalance;

View File

@@ -95,6 +95,7 @@ QByteArray RecentPeers::serialize() const {
void RecentPeers::applyLocal(QByteArray serialized) {
_list.clear();
if (serialized.isEmpty()) {
DEBUG_LOG(("Suggestions: Bad RecentPeers local, empty."));
return;
}
auto stream = Serialize::ByteArrayReader(serialized);
@@ -102,8 +103,13 @@ void RecentPeers::applyLocal(QByteArray serialized) {
auto count = quint32();
stream >> streamAppVersion >> count;
if (!stream.ok()) {
DEBUG_LOG(("Suggestions: Bad RecentPeers local, not ok."));
return;
}
DEBUG_LOG(("Suggestions: "
"Start RecentPeers read, count: %1, version: %2."
).arg(count
).arg(streamAppVersion));
_list.reserve(count);
for (auto i = 0; i != int(count); ++i) {
const auto peer = Serialize::readPeer(
@@ -114,9 +120,15 @@ void RecentPeers::applyLocal(QByteArray serialized) {
_list.push_back(peer);
} else {
_list.clear();
DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2."
).arg(i + 1
).arg(count));
_list.clear();
return;
}
}
DEBUG_LOG(
("Suggestions: RecentPeers read OK, count: %1").arg(_list.size()));
}
} // namespace Data

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