Compare commits

..

289 Commits

Author SHA1 Message Date
John Preston
63919422e0 Version 3.2: Fix build error on Linux. 2021-11-03 17:56:32 +04:00
John Preston
d6e6c51639 Version 3.2.
- Create special invite links that require admins
to approve users before they become members.
- Admins can view the applicants' profiles and bios
by tapping the Join Requests bar at the top of the chat.
- Add internal labels to your chat's Invite Links
to keep them organized.
- More Interactive Emoji - 👻, :dislike:, :face_vomiting:,
😂, 💸 or 🎃
2021-11-03 16:38:14 +04:00
John Preston
95a24d6aa1 Don't limit invite import aboutRequests text. 2021-11-03 16:27:51 +04:00
John Preston
fa28b0b405 Fallback to "Desktop" instead of "PC". 2021-11-03 16:24:37 +04:00
John Preston
42fcf4ceb2 Add patch system version component on macOS. 2021-11-03 16:18:31 +04:00
John Preston
3743dd0161 Fix small window icon counter.
Regression was introduced in aef45b3a1d.
2021-11-03 16:14:20 +04:00
John Preston
dab859ea29 Fix sponsored messages about text layout. 2021-11-03 16:01:38 +04:00
John Preston
a92394a81f Hide "Request admin approval" in public peers. 2021-11-03 16:01:25 +04:00
John Preston
7a57174ab1 Register sponsored view only on full message show. 2021-11-03 15:49:48 +04:00
23rd
52bacb3cde Improved tracking of views for sponsored messages. 2021-11-02 20:58:35 +03:00
23rd
8ad9770118 Fixed missed CallId alias. 2021-11-02 20:58:35 +03:00
John Preston
aef45b3a1d Refactor icon unread counter painting. 2021-11-02 20:12:57 +04:00
John Preston
87af865604 Fix warning about inactive QPainter. 2021-11-02 20:12:51 +04:00
John Preston
4efeaacf5c Add send context menu to StickerSetBox. 2021-11-02 20:11:20 +04:00
John Preston
e73928f6a3 Beta version 3.1.13.
- Fix requests to groups / channels processing.
- Fix internal link previews with View Content button layout.
- Fix crash in messages search with imported messages results.
- Don't use fractional system UI scaling on Linux.
- Fix invite link icons on macOS.
- Several crash fixes.
2021-11-02 11:28:53 +04:00
John Preston
589ddb7b22 Add network reachability plugin on macOS. 2021-11-02 11:16:54 +04:00
John Preston
6d73b11d54 Move "Request approval" button to the top. 2021-11-02 10:22:12 +04:00
John Preston
81ff4d7497 Show joined list on permanent link "N joined" click. 2021-11-02 10:10:48 +04:00
John Preston
e4ac810773 Allow exactly 1x20 aspect in photos. 2021-11-02 09:59:06 +04:00
John Preston
7e9302fce8 Try linking with -z,now on Linux once again. 2021-11-02 09:43:20 +04:00
John Preston
bd2fe2f68e Debug std::isnan assertion violation. 2021-11-02 09:37:33 +04:00
John Preston
e73c049899 Fix crash in PopupMenu hide on macOS. 2021-11-02 00:18:57 +04:00
John Preston
dab3bce8ba Fix crash on macOS before the app starts. 2021-11-02 00:07:22 +04:00
John Preston
eae6e4fc60 Fix build on non-Windows. 2021-11-02 00:06:56 +04:00
John Preston
79a8fced80 Fix scrolling by Page Up / Down by Qt 6 patch. 2021-11-01 16:57:40 +04:00
John Preston
3bd9ccd45d Don't generate dSYM for macOS build. 2021-11-01 16:57:40 +04:00
John Preston
1bfa97d4fd Fix invite link icons on macOS Retina. 2021-11-01 16:57:40 +04:00
John Preston
3b4b5d3576 Don't activate PeerListRow elements on mouse down. 2021-11-01 16:57:40 +04:00
John Preston
98cb1478c7 Fix requests list processing. 2021-11-01 16:04:30 +04:00
John Preston
099326f211 Don't set window border color on Windows 11.
When set it can only be opaque which doesn't look great.
When left default it is made of a semi-transparent shadow color.
2021-11-01 15:03:28 +04:00
John Preston
8b4a95826c Fix webpage layout with ViewButton. 2021-11-01 14:54:44 +04:00
Ilya Fedin
27a9b61f72 Avoid Qt's fractional scaling 2021-11-01 13:12:13 +04:00
GitHub Action
bad193ec29 Update User-Agent for DNS to Chrome 94.0.4606.81. 2021-11-01 13:12:00 +04:00
John Preston
60444b1fee Fix message ViewButton rendering in 1x. 2021-11-01 13:11:34 +04:00
John Preston
65d96c0364 Fix crash in imported messages in search. 2021-11-01 13:11:34 +04:00
John Preston
28f85eb710 Allow running custom commands through prepare script. 2021-11-01 11:51:49 +04:00
John Preston
63485dbf7f Change sponsored messages about link. 2021-11-01 11:50:55 +04:00
John Preston
d58b9dc954 Re-enable -pie along with AppImage-like format hack.
Fixes #1112.

This allows the binary to be launched even when compiled with -pie.
2021-10-31 16:49:44 +04:00
John Preston
7b8b50ecd5 Beta version 3.1.12.
- Create special invite links that require admins
to approve users before they become members.
- Admins can view the applicants' profiles and bios
by tapping the Join Requests bar at the top of the chat.
- Add internal labels to your chat's Invite Links
to keep them organized.
- Run natively on Apple Silicon (macOS only).
2021-10-31 12:33:22 +04:00
John Preston
5cbfdad2e8 Separate strings for "requested to join {date}". 2021-10-31 12:33:22 +04:00
John Preston
51addb7320 Fix build on Windows 64 bit. 2021-10-31 12:28:22 +04:00
John Preston
e652b4b65e Improve DeviceModelPretty. 2021-10-31 11:31:13 +04:00
John Preston
510e1e9cdf Fix one more unused variable warning. 2021-10-31 11:01:39 +04:00
John Preston
057e8ce391 Fix drag by title on macOS. 2021-10-31 10:14:00 +04:00
John Preston
1d655fb5b5 Fix build on Windows and Linux CI. 2021-10-31 10:07:12 +04:00
John Preston
5761e7559a Fix compilation error on GCC. 2021-10-31 09:45:43 +04:00
23rd
c55dc00180 Added selecting scroll to sections.
Fixed #17136.
2021-10-30 21:22:51 +03:00
23rd
27dc91e51a Moved management of selecting scroll to separated class in td_ui. 2021-10-30 21:22:51 +03:00
23rd
6ef67caa73 Fixed emoji clearing in sections when sending failed due to slowmode.
Fixed #17149.
2021-10-30 21:22:51 +03:00
Ilya Fedin
b36fce31d5 Add missing libSM and libICE to snap 2021-10-30 21:46:48 +04:00
John Preston
aaafa8b3f6 Fix compilation error on GCC. 2021-10-30 17:19:58 +04:00
John Preston
b4950fe943 Fix build for Mac App Store. 2021-10-30 17:19:24 +04:00
John Preston
7ad6699bff Show mouse hold tip when clicking record button. 2021-10-30 15:13:18 +04:00
John Preston
05efc925f8 Fix CMake warning when running on Windows. 2021-10-30 15:03:45 +04:00
23rd
06c9cac2e4 Updated Qt license. 2021-10-30 14:20:20 +04:00
Ilya Fedin
4b33d7cb2f Fix Qt dependencies in snap
Qt uses libOpenGL and libGLX instead of libGL now
2021-10-30 14:02:37 +04:00
John Preston
9c0bf32b84 Fix tg_owt build in prepare script on Windows. 2021-10-29 22:29:05 +04:00
Ilya Fedin
2a504dd90d Update lib_base 2021-10-29 22:06:46 +04:00
Ilya Fedin
97881e7754 Default to Qt 6 on Linux and macOS 2021-10-29 22:06:46 +04:00
Ilya Fedin
e42f08f08d Switch snap to Qt 6 2021-10-29 22:06:46 +04:00
John Preston
42015d6a72 Use new defaults in configure invocations. 2021-10-29 22:05:21 +04:00
John Preston
094010a91d Fix Breakpad + remove Crashpad build on Windows. 2021-10-29 22:01:04 +04:00
John Preston
f71a2149f3 Improve defaults in prepare script. 2021-10-29 21:21:33 +04:00
John Preston
1297860652 Fix build of minidump_stackwalk. 2021-10-29 21:11:23 +04:00
John Preston
c528ea24ea Version 3.1.11.
- Create special invite links that require admins to approve users before they become members.
- Admins can view the applicants' profiles and bios by tapping the Join Requests bar at the top of the chat.
- Add internal labels to your chat's Invite Links to keep them organized.
- Run natively on Apple Silicon (macOS only).
2021-10-29 19:06:19 +04:00
John Preston
fa8792927e Improve invite link name phrases. 2021-10-29 18:31:07 +04:00
John Preston
aaae5b0553 Support invite link label editing. 2021-10-29 18:31:07 +04:00
John Preston
eb82664452 Use recent requester userIds from API. 2021-10-29 18:31:07 +04:00
John Preston
36271d6b85 Save recent requester userIds from API. 2021-10-29 18:31:07 +04:00
John Preston
f839c7f2bb Update API scheme on layer 134. 2021-10-29 18:31:07 +04:00
John Preston
94dc595a81 Show full profile from group members list. 2021-10-29 18:31:07 +04:00
John Preston
2f4651fe6f Fix build on macOS. 2021-10-29 18:31:07 +04:00
John Preston
97ae094c3c Update API scheme with new cloud themes. 2021-10-29 18:31:07 +04:00
John Preston
045689fab1 Highlight links in about. 2021-10-29 18:31:07 +04:00
John Preston
47d957f942 Add 5 lines of About to group call context menu. 2021-10-29 18:31:07 +04:00
John Preston
d0606a3798 Show PeerShortInfoCover in group call context menu. 2021-10-29 18:31:07 +04:00
John Preston
bcddda3cd3 Extract PeerShortInfoCover from the box. 2021-10-29 18:31:07 +04:00
John Preston
b5f50a4b9f Preload photos in PeerShortInfoBox. 2021-10-29 18:31:07 +04:00
John Preston
9c77f26a8b Show radial progress in PeerShortInfoBox photo. 2021-10-29 18:31:07 +04:00
John Preston
d73d5724d8 Show radial progress in PeerShortInfoBox video. 2021-10-29 18:31:07 +04:00
John Preston
9f21da8bde Display profile video playback progress. 2021-10-29 18:31:07 +04:00
John Preston
6b137b9778 Make full PeerShortInfoBox scrollable. 2021-10-29 18:31:07 +04:00
John Preston
c9e5eadb06 Use a detailed phrase for single user join request. 2021-10-29 18:31:07 +04:00
John Preston
05bdef041b Improve PeerShortInfoBox cover updating. 2021-10-29 18:31:07 +04:00
John Preston
360a92c198 Show user photos overview in PeerShortInfoBox. 2021-10-29 18:31:07 +04:00
John Preston
64f6b86739 Return local search to requests box. 2021-10-29 18:31:07 +04:00
John Preston
dcc14a4726 Add info rows to PeerShortInfoBox. 2021-10-29 18:31:07 +04:00
John Preston
2ca5f26546 Don't show "You:" in private chats. 2021-10-29 18:31:07 +04:00
John Preston
24e0ea2a59 Show profile video in PeerShortInfoBox. 2021-10-29 18:31:07 +04:00
John Preston
61ac7e6c1d Start PeerShortInfoBox for small in-box profiles. 2021-10-29 18:31:06 +04:00
John Preston
49b28ac695 Reuse "info_add_member" icon for requests management. 2021-10-29 18:31:06 +04:00
John Preston
2163957299 Fix empty chat history after custom themed chat. 2021-10-29 18:31:06 +04:00
John Preston
2e1981c5a6 Improve confirm join box design. 2021-10-29 18:31:06 +04:00
John Preston
ab60628386 Usage limit and requests are mutually exclusive. 2021-10-29 18:31:06 +04:00
John Preston
2ade6be146 Show correct phrase in local join messages. 2021-10-29 18:31:06 +04:00
John Preston
721aac57a5 Fix processing requests from search results. 2021-10-29 18:31:06 +04:00
John Preston
8618f6d7eb Push recent requests from requests box to the bar. 2021-10-29 18:31:06 +04:00
John Preston
7543351bc9 Add pending requests bar in the chat. 2021-10-29 18:31:06 +04:00
John Preston
0dfbd5fa6e Allow processing requests from full list. 2021-10-29 18:31:06 +04:00
John Preston
9dfbc96274 Display full list of requests. 2021-10-29 18:31:06 +04:00
John Preston
ab58aa020e Allow many custom elements in PeerListRow. 2021-10-29 18:31:06 +04:00
John Preston
7f428f2eeb Show full requests list in a box. 2021-10-29 18:31:06 +04:00
John Preston
b4895ef730 Allow to accept / reject requests by link. 2021-10-29 18:31:06 +04:00
John Preston
9e05e44a14 Inform about join request being sent. 2021-10-29 18:31:06 +04:00
John Preston
3af3f85f82 Allow creating approve-only invite links. 2021-10-29 18:31:06 +04:00
John Preston
e471d61d7a Enable ads leading to a specific channel post. 2021-10-29 18:31:06 +04:00
John Preston
185523f66f Update API scheme to layer 134. 2021-10-29 18:31:06 +04:00
John Preston
66a83d3862 Update build script for macOS. 2021-10-29 18:29:29 +04:00
John Preston
ad9d15dd21 Remove debug code from resource loading. 2021-10-29 17:42:57 +04:00
John Preston
f204b9fca0 RegisterBundledResources only on macOS. 2021-10-29 17:42:57 +04:00
John Preston
a3e3bcd46d Fix repeated phoneCallRequested update handling. 2021-10-29 17:42:57 +04:00
John Preston
c693b03a64 Update breakpad build in Windows CI. 2021-10-29 17:42:57 +04:00
John Preston
31f15a2f09 Beta version 3.1.10.
- Native support for M1 on macOS.
2021-10-29 16:11:15 +04:00
John Preston
45b5e1241c Show device model in sessions list on Windows / Linux. 2021-10-29 16:11:15 +04:00
John Preston
43d42b54f8 Closed alpha version 3.1.9.5: Separate macOS updates. 2021-10-29 16:10:07 +04:00
John Preston
090277d7a1 Upload a separate macOS ARM update. 2021-10-29 16:10:07 +04:00
John Preston
766b393295 On macOS build autoupdates single-arch + universal setup. 2021-10-29 16:10:07 +04:00
John Preston
b2d647b579 Add support for fcitx-qt5 on Qt 6.2. 2021-10-29 16:10:07 +04:00
John Preston
1ed6844247 Use Qt resources from a file on macOS. 2021-10-29 16:10:07 +04:00
John Preston
5276e5b4ae Build minidump_stackwalk separately.
On macOS gyp is no longer required for non-official builds.
2021-10-29 16:10:07 +04:00
John Preston
6587f89db1 Update Breakpad to the latest commit. 2021-10-29 16:10:07 +04:00
John Preston
fb262b265b Link a single crashpad_client library. 2021-10-29 16:10:07 +04:00
John Preston
95074ef304 Closed alpha version 3.1.9.2 for macOS. 2021-10-29 16:10:07 +04:00
John Preston
4ac93806aa Fix emoji in the input fields on macOS with Qt 6.2. 2021-10-29 16:10:07 +04:00
John Preston
92b3149cdd Pass CMAKE_OSX_ARCHITECTURES from command line to configure. 2021-10-29 16:10:07 +04:00
John Preston
98c87d4a16 Dump symbols for both macOS builds. 2021-10-29 16:10:07 +04:00
John Preston
9a93d5811a Closed alpha version 3.1.9.1: Universal 2 macOS build. 2021-10-29 16:10:07 +04:00
John Preston
759e3270cc Fix popup menu transparency on macOS with Qt 6.2. 2021-10-29 16:10:07 +04:00
John Preston
746b72166f Fix main window drag-by-title on macOS with Qt 6.2. 2021-10-29 16:10:07 +04:00
John Preston
0292df12ef Working Universal 2 build. 2021-10-29 16:10:07 +04:00
John Preston
1e86c07505 Build FFMpeg as Universal 2 binaries. 2021-10-29 16:10:07 +04:00
John Preston
47b6956be9 Update crashpad to a new revision. 2021-10-29 16:10:07 +04:00
John Preston
df8708ef1e Build some dependencies as Universal 2 binaries. 2021-10-29 16:10:07 +04:00
John Preston
20c0be0df6 Use python from a correct folder. 2021-10-29 16:10:07 +04:00
23rd
cefdc29a7f Fixed release build of Breakpad on clean macOS. 2021-10-29 16:10:07 +04:00
23rd
70e9b4a332 Moved type of call id to alias. 2021-10-29 16:10:07 +04:00
23rd
08939ac51d Fixed display of edit badge in albums. 2021-10-29 16:10:07 +04:00
23rd
b5d9947408 Slightly improved code style in Calls:Call. 2021-10-29 16:10:07 +04:00
23rd
78f0cf908e Moved RateCallBox to td_ui. 2021-10-29 16:10:07 +04:00
23rd
93ae5e71f7 Guarded timers for online processing in touchbar's PinnedDialogButton. 2021-10-29 16:10:07 +04:00
Ilya Fedin
b8b268c7cc Add build architecture to version on other systems with non-x86_64 2021-10-29 16:02:41 +04:00
Ilya Fedin
c991bbc7e3 Reduce obtrusiveness of the lock bot 2021-10-29 16:02:15 +04:00
Ilya Fedin
45bbe33929 Multiple scale values in UI by device pixel ratio on non-Mac
To avoid confusion
2021-10-25 19:55:11 +04:00
John Preston
5aaa72e8cd Fix crash in scheduled emoji interactions.
Fixes #17093.
2021-10-25 09:43:22 +04:00
John Preston
57345cec3b Discard incoming calls by ring_timeout (1.5 minutes). 2021-10-25 09:43:12 +04:00
John Preston
fba17a8c25 Update description in lib/xdg/telegramdesktop.appdata.xml.in. 2021-10-25 09:23:36 +04:00
Ilya Fedin
70147922ae Re-enable kwayland with Qt 6 2021-10-25 08:59:43 +04:00
Ilya Fedin
44cc3c7809 Update submodules 2021-10-24 20:01:55 +04:00
Ilya Fedin
c50a5db277 Restore dbusmenu-qt dependent functionality with Qt 6 2021-10-24 20:01:55 +04:00
John Preston
389ea2af83 Fix compilation error on Linux. 2021-10-22 18:34:59 +04:00
John Preston
4cf9bf18e9 Fix clearing of macOS native notifications. 2021-10-21 23:37:14 +04:00
John Preston
7bc4b2c595 Add StickersSet::thumbnailBigFileBaseCacheKey. 2021-10-21 23:01:14 +04:00
John Preston
9075489c18 Use last history item date for jump-to-date. 2021-10-21 23:01:13 +04:00
John Preston
d361f5c6b0 Hide native notifications of deleted messages. 2021-10-21 23:01:13 +04:00
John Preston
1f95e00793 Fix "Copy Selected Text" appearing outside of selection. 2021-10-21 22:59:49 +04:00
John Preston
3d8899b9dc Update tg_owt revision. 2021-10-21 22:53:12 +04:00
John Preston
dba9aa30f7 Fix build action on macOS. 2021-10-21 21:01:33 +04:00
John Preston
3626943fc9 Use Qt 6.2 build on macOS GitHub action. 2021-10-21 20:20:35 +04:00
John Preston
1f6a9ab556 Fix prepare libraries on macOS with Qt 6.2. 2021-10-21 20:19:56 +04:00
John Preston
f7085b40b1 Support building macOS version with Qt 6.2. 2021-10-21 19:49:48 +04:00
Ilya Fedin
7222bc63f7 Update MozJPEG 2021-10-21 19:31:22 +04:00
Ilya Fedin
3dacbc6bf6 Ignore changes to Dockerfile on mac 2021-10-21 19:31:22 +04:00
Ilya Fedin
9dfa29ff0f Avoid using scl binary since it breaks conditions 2021-10-21 18:31:08 +04:00
Ilya Fedin
a1e67b6177 Fix failing on exit code check 2021-10-21 15:51:36 +04:00
Ilya Fedin
03a687c200 Adapt Dockerfile for Qt 6 2021-10-21 15:43:59 +04:00
Ilya Fedin
847c01d605 Add Qt 6 support
Tested only on Linux so far
2021-10-21 13:15:00 +04:00
23rd
ea10cf5758 Moved api polls processing to separated file. 2021-10-20 22:56:19 +03:00
23rd
159beb138a Moved api peer photo processing to separated file.
Removed MainWidget::deletePhotoLayer.
2021-10-20 22:56:19 +03:00
23rd
36d6682122 Improved code style in ChangePhoneBox. 2021-10-20 04:06:17 +03:00
23rd
25f6bea66e Moved ConfirmPhoneBox to td_ui. 2021-10-19 06:34:28 +03:00
23rd
80461bd9fe Moved ConfirmBox to Ui namespace. 2021-10-19 06:34:27 +03:00
23rd
6148edbc7d Moved ConfirmBox to td_ui. 2021-10-19 06:34:27 +03:00
23rd
3fa529d858 Extracted MaxInviteBox to separated file. 2021-10-19 06:34:27 +03:00
23rd
9117b3cdfa Extracted DeleteMessagesBox to separated file. 2021-10-19 06:34:27 +03:00
23rd
d4fe5f7a83 Extracted PinMessageBox to separated file. 2021-10-19 06:34:27 +03:00
23rd
fa6725c54a Extracted api code from ConfirmPhoneBox to separated file. 2021-10-19 06:34:27 +03:00
23rd
f9976005f7 Moved ShowPhoneBannedError to separated file. 2021-10-19 06:34:27 +03:00
23rd
c6e1b14429 Added Window::SessionController to ChangePhoneBox. 2021-10-19 06:34:27 +03:00
23rd
30681e2e58 Moved widget sliders to td_ui. 2021-10-19 06:34:27 +03:00
23rd
c15ba7d23a Moved SentCodeField to td_ui. 2021-10-19 06:34:27 +03:00
23rd
94d5d20281 Simplified saving self bio. 2021-10-19 06:34:27 +03:00
23rd
b776308fd7 Moved some constants for peer editing to separated file. 2021-10-19 06:34:27 +03:00
23rd
2d37920a4c Improved code style in some peer boxes. 2021-10-19 06:34:27 +03:00
23rd
ee05e0af06 Improved code style in UsernameBox. 2021-10-19 06:34:27 +03:00
23rd
2efd735243 Removed static storing of passport config. 2021-10-14 17:41:52 +03:00
John Preston
adb0a9b6f0 Fix document filenames.
Regression was introduced in 2b11e45692.
2021-10-11 22:58:24 +04:00
CoderTimZ
c9e24c2283 Display dates with the system date format 2021-10-11 21:56:29 +04:00
John Preston
041c922451 Add non-MTP ParseWebPageType. 2021-10-11 21:54:07 +04:00
John Preston
01c1096c62 Save Data::Session* in GameData. 2021-10-11 21:53:18 +04:00
John Preston
2b11e45692 Add DocumentData::setFileName. 2021-10-11 21:52:49 +04:00
23rd
6163e922b3 Added view button to webpages. 2021-10-10 19:30:38 +03:00
John Preston
1613495425 Version 3.1.9.
- Fix crash in chat closing while scrolling (macOS only).
2021-10-08 22:37:36 +04:00
23rd
455c7280a4 Fixed possible crash in scroll of empty HistoryWidget. 2021-10-08 19:10:39 +03:00
John Preston
746f8d835d Version 3.1.8.
- Show small media previews in chats list.
- Show media album previews and caption text in chats list.
- Add "Quick Reply" and "Mark as Read" to native Windows notifications.
2021-10-08 13:51:22 +04:00
John Preston
d66e9a1b00 Remove -pie linker flag.
See https://gitlab.gnome.org/GNOME/nautilus/-/issues/1601
2021-10-08 13:50:27 +04:00
John Preston
8cca75da5c Handle foreign instance in COM toast activator. 2021-10-08 13:46:52 +04:00
John Preston
8d0ff1b61d Try setting current window as foreground on activate. 2021-10-08 12:20:24 +04:00
John Preston
dd856b9e4a Use real QWindow for taskbar icon hider.
Fixes #17081.
2021-10-08 12:19:41 +04:00
23rd
eb5ba12ba3 Fixed ability to copy original caption in admin log.
Fixed #17076.
2021-10-08 10:39:36 +04:00
23rd
e2c5995a2e Fixed timestamp highlighting in albums.
Fixed #17078.
2021-10-08 10:39:36 +04:00
23rd
ef10bb2bd6 Fixed editing caption of album from context menu.
Fixed #17077.
2021-10-08 10:39:36 +04:00
23rd
64aa5480ad Fixed display edited badge in scheduled albums.
Moved hideEditedBadge from HistoryMessage to HistoryItem.
2021-10-08 10:39:36 +04:00
23rd
816f422e21 Removed MTP* from applying message edition.
Fixed #17073.
2021-10-08 10:39:36 +04:00
23rd
6c0dccd9ff Fixed replying in replies by double click.
Fixed #16645.
2021-10-08 10:39:36 +04:00
23rd
c2b505b78c Added ability to open specific post from sponsored messages. 2021-10-08 10:39:27 +04:00
23rd
d8fb5be9b5 Moved opening peer from PeerClickHandler to SessionController. 2021-10-07 23:32:10 +03:00
23rd
51b259fdea Updated ad description. 2021-10-07 23:32:10 +03:00
John Preston
d532b65d1c Don't use MTP* for replies data. 2021-10-07 22:57:44 +04:00
John Preston
bef35b9bc3 Don't use MTP* in call and invoice media data. 2021-10-07 18:22:28 +04:00
John Preston
ae261fcede Beta version 3.1.7.
- Fix channel message views and comments counter updates.
- Sponsored messages support.
- Crash fix.
2021-10-07 11:42:24 +04:00
John Preston
c04cdff7f7 Disable group call logs to console. 2021-10-07 11:42:24 +04:00
John Preston
466aa5a14d Fix view button style. 2021-10-07 11:38:07 +04:00
John Preston
4aac633413 Fix views increment.
Regression was introduced in 21aa1f49d7.

Fixes #17069.
2021-10-07 11:29:30 +04:00
23rd
ad328d35a2 Added box for ad description. 2021-10-07 11:09:09 +04:00
23rd
c5140f34a7 Added view button to sponsored messages. 2021-10-07 11:09:09 +04:00
23rd
419f6345b3 Added sponsored messages to HistoryWidget. 2021-10-07 11:09:09 +04:00
23rd
c2c53df886 Added new Ui::СontinuousScroll. 2021-10-07 11:09:09 +04:00
23rd
b3f73bb6a9 Added badge for sponsored messages. 2021-10-07 11:09:08 +04:00
23rd
eda5cd47ad Added manager of sponsored messages. 2021-10-07 11:09:08 +04:00
John Preston
0c906a5e6d Fix crash in local changelog messages. 2021-10-07 10:58:24 +04:00
John Preston
352768053d Beta version 3.1.6: Fix build on Linux. 2021-10-06 21:07:18 +04:00
John Preston
79b1cec4f3 Beta version 3.1.6: Remove -z,all from linker flags on Linux.
I hope it fixes #17037.
2021-10-06 20:37:39 +04:00
John Preston
8d09190439 Build ffmpeg without --disable-alsa/iconv on Linux. 2021-10-06 20:37:30 +04:00
John Preston
5cd0a3719e Beta version 3.1.6: Detach FastReply from MarkAsRead. 2021-10-06 19:24:30 +04:00
John Preston
8b7cd4a0c7 Beta version 3.1.6: Fix crash on old Windows 10 versions. 2021-10-06 17:36:30 +04:00
John Preston
937c2d3dce Beta version 3.1.6: Update patches revision. 2021-10-06 13:44:28 +04:00
23rd
1fa5d273cc Fixed scroll in Dialogs::Widget.
Regression was introduced in cb8f49aea0.
2021-10-06 13:40:26 +04:00
John Preston
24fa3dbf8f Beta version 3.1.6: Fix build on macOS. 2021-10-06 11:10:06 +04:00
John Preston
c9b782fd63 Beta version 3.1.6.
- Show small media previews in chats list.
- Show media album previews and caption text in chats list.
- Add "Quick Reply" and "Mark as Read" to native Windows notifications.
2021-10-06 11:07:38 +04:00
John Preston
e7cf560da0 Handle toast activations by COM activator. 2021-10-06 11:02:57 +04:00
John Preston
86e07518ad Fix clearing notifications from Action Center.
Regression was introduced in 997913be25.
2021-10-05 16:53:36 +04:00
John Preston
8c71d03959 Add reply from Windows native notifications. 2021-10-05 16:52:46 +04:00
John Preston
967e86f4ab Rewrite Windows native notifications using C++/WinRT. 2021-10-05 12:09:15 +04:00
John Preston
730412fefe Load albums of last chat messages. 2021-10-04 23:47:33 +04:00
John Preston
576883ddc8 Make mini preview radius 2px. 2021-10-04 23:47:33 +04:00
John Preston
992d636680 Generate album mini previews with up-to-three images. 2021-10-04 23:47:33 +04:00
John Preston
8cdd2f113f Add play icon to video mini previews. 2021-10-04 23:47:33 +04:00
John Preston
d5f935b73d Put mini-previews after sender name. 2021-10-04 23:47:33 +04:00
John Preston
84f561b251 Don't use MTP* in the image editor. 2021-10-04 23:47:33 +04:00
John Preston
21ac2b8f3a Don't use MTP* for reply markup data. 2021-10-04 23:47:30 +04:00
John Preston
1790828b01 Dump symbols from the binary instead of dSYM.
For some reason dump_syms from dSYM now fails with an error:

Telegram.app.dSYM/Contents/Resources/DWARF/Telegram:
the section '__text' in segment '__TEXT' claims its contents lie outside the segment's contents
2021-10-04 23:45:21 +04:00
John Preston
792b9090a7 Generate mini-previews for photos and files. 2021-10-04 23:45:21 +04:00
John Preston
8c21fad642 Move preview paint to Dialogs::Ui::MessageView. 2021-10-04 23:45:21 +04:00
John Preston
5136cc3c9c Rename Dialogs::Layout to Dialogs::Ui. 2021-10-04 23:45:21 +04:00
John Preston
b78b27f517 Move dialogs_layout to dialogs/ui/. 2021-10-04 23:45:21 +04:00
John Preston
85760ea92c Fix repeated attempt to transfer ownership.
Fixes #8570.
2021-10-04 23:45:21 +04:00
Ilya Fedin
c2212c719e Inform Qt about taskbar hider
This allows the feature to work without patching Qt
2021-10-04 23:23:50 +04:00
GitHub Action
fc8a0d0efd Update User-Agent for DNS to Chrome 94.0.4606.61. 2021-10-04 23:23:20 +04:00
23rd
c052c37621 Fixed build for non-Windows. 2021-09-30 21:04:43 +03:00
23rd
21f7cec781 Fixed build for macOS. 2021-09-30 21:21:07 +04:00
23rd
64af456d29 Fixed build for macOS. 2021-09-30 21:16:45 +04:00
John Preston
7751c4ac1f Port PQ factorization from TDLib. 2021-09-30 21:14:00 +04:00
23rd
ececdcb9c0 Removed Q_SLOTS from HistoryInner. 2021-09-30 18:45:38 +03:00
23rd
cb8f49aea0 Removed Q_OBJECT from ScrollArea. 2021-09-30 18:45:38 +03:00
23rd
e3ef7d6631 Removed MainWidget::highlightStartTime. 2021-09-30 18:45:38 +03:00
23rd
21aa1f49d7 Moved views increment scheduler from MainWidget to separate file. 2021-09-30 18:45:38 +03:00
John Preston
51e80170e2 Always clear passcode lock widget on reset.
Fixes #17016.
2021-09-30 19:37:42 +04:00
John Preston
b2526ab7f6 Make sure special MsgId-s are always outside ServerMaxMsgId range. 2021-09-30 19:31:03 +04:00
John Preston
e220447bdd Put the "N Seen" context menu item always first. 2021-09-30 15:36:14 +04:00
John Preston
ead695b101 Don't pass wide fake MsgId through MTPMessage. 2021-09-30 15:30:39 +04:00
John Preston
4ea72f8f89 Don't add "Change colors" to profile menu. 2021-09-30 15:30:29 +04:00
John Preston
4ef550da9b Hide webview while showing a box in payments. 2021-09-30 14:53:18 +04:00
John Preston
1e660fc2a2 Allocate 64 bits for message ids. 2021-09-30 13:49:37 +04:00
Ilya Fedin
6adf791b3b Update cmake_helpers 2021-09-29 09:04:23 +04:00
Ilya Fedin
d2a41a42e0 Move applicationDidFinishLaunching code to init
This should make the Qt patch catching the event unneeded
2021-09-29 09:04:23 +04:00
John Preston
315549b5f8 Beta version 3.1.5: Fix build on Linux. 2021-09-28 23:48:37 +04:00
John Preston
fd4a543bab Beta version 3.1.5: Fix theme change UI on Retina screens. 2021-09-28 22:27:41 +04:00
John Preston
d525e56053 Beta version 3.1.5: Fix build on Linux. 2021-09-28 22:08:28 +04:00
John Preston
dab5d1f994 Beta version 3.1.5.
- Choose one of 8 new preset themes for any individual private chat.
- Click on '...' menu > 'Change Colors' to pick a theme.
- Both chat participants will see the same theme
in that chat – on all their devices.
- Each new theme features colorful gradient message bubbles,
beautifully animated backgrounds and unique background patterns.
- All chat themes have day and night versions
and will follow your overall dark mode settings.
- Implement main window rounded corners on Windows 11.
- Fix audio capture from AirPods on macOS.
2021-09-28 22:00:51 +04:00
23rd
de3b52425c Removed unused HistoryInner::setFirstLoading. 2021-09-28 21:14:33 +04:00
John Preston
844fd58a97 Support Windows 11 rounded corners and themeable title bar. 2021-09-28 21:11:35 +04:00
John Preston
de2bad51d3 Scroll to currently selected theme. 2021-09-28 19:27:41 +04:00
John Preston
1424ea3540 Allow scrolling themes list. 2021-09-28 19:27:41 +04:00
John Preston
a8efd0ef3d Show chosen element in custom theme selector. 2021-09-28 19:27:41 +04:00
John Preston
1204e282d3 Fix attach icon in theme preview. 2021-09-28 19:27:41 +04:00
John Preston
6588242793 Prepare correct custom chat theme preview. 2021-09-28 19:27:41 +04:00
John Preston
b1ba9a42c6 Use Ui::GenerateBackgroundImage for preview in Settings. 2021-09-28 19:27:41 +04:00
John Preston
ab0d2bf9c6 Initial chat theme changing. 2021-09-28 19:27:41 +04:00
John Preston
80028e41f3 Bump OpenAL version in prepare script. 2021-09-28 19:25:39 +04:00
John Preston
2c581adc55 Add some hardening compiler / linker flags to dependencies. 2021-09-28 18:44:52 +04:00
John Preston
f0e8c1e325 Update lib_webview and docker patches revision. 2021-09-28 12:23:54 +04:00
John Preston
a2db9de4d7 Remove debug code. 2021-09-28 11:30:18 +04:00
John Preston
a228c62286 Fix "Nobody Viewed / Watched / Listened" seen state. 2021-09-27 18:51:50 +04:00
John Preston
37d940eca6 Beta version 3.1.4.
- Fix crash in network availability init.
- Fix assertion violation after a NaN-resulting std::round call.
2021-09-27 13:29:11 +04:00
John Preston
f7c24c54a1 Fix crash in failed network availability init. 2021-09-27 13:25:04 +04:00
John Preston
19ce1edc16 Use base::SafeRound instead of std::round.
Previous assertion violations because of NaN from std::round were
in video streaming, see commits 27d58ba07b, 8f5830d520.

Now the crashes happened in the ConvertScale() call from a background
thread when preparing an image from clipboard for sending to a chat.
2021-09-27 12:13:57 +04:00
John Preston
21b10cebe0 Beta version 3.1.3.
- Fix illegal instruction crash in opus encoder.
2021-09-27 10:28:29 +04:00
John Preston
50435f7783 Allow creating links with Ip addresses. 2021-09-27 10:28:29 +04:00
Ilya Fedin
1b789de4f4 Cherry-pick a opus fix for detecting CPU instructions on Windows 2021-09-27 08:50:51 +04:00
519 changed files with 17639 additions and 7592 deletions

View File

@@ -13,11 +13,3 @@ jobs:
github-token: ${{ github.token }}
issue-lock-inactive-days: 45
pr-lock-inactive-days: 45
issue-lock-comment: >
This issue has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
pr-lock-comment: >
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.

View File

@@ -13,6 +13,7 @@ on:
- '!.github/workflows/mac.yml'
- 'lib/xdg/**'
- 'snap/**'
- 'Telegram/build/docker/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'
@@ -30,6 +31,7 @@ on:
- '!.github/workflows/mac.yml'
- 'lib/xdg/**'
- 'snap/**'
- 'Telegram/build/docker/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'

View File

@@ -185,7 +185,7 @@ jobs:
- name: MozJPEG.
shell: cmd
run: |
git clone -b v4.0.1-rc2 %GIT%/mozilla/mozjpeg.git
git clone -b v4.0.3 %GIT%/mozilla/mozjpeg.git
cd mozjpeg
cmake . ^
-G "Visual Studio 16 2019" ^
@@ -236,10 +236,10 @@ jobs:
git clone https://chromium.googlesource.com/breakpad/breakpad
cd breakpad
git checkout bc8fb886
git checkout dfcb7b6799
git apply ../patches/breakpad.diff
cd src
git clone %GIT%/google/googletest testing
git clone -b release-1.11.0 %GIT%/google/googletest testing
cd client\windows
call gyp --no-circular-check breakpad_client.gyp --format=ninja
cd ..\..
@@ -261,6 +261,7 @@ jobs:
run: |
git clone -b v1.3.1 %GIT%/xiph/opus.git
cd opus
git cherry-pick 927de8453c
cmake -B out . ^
-A Win32 ^
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^

View File

@@ -39,6 +39,7 @@ include(cmake/init_target.cmake)
include(cmake/generate_target.cmake)
include(cmake/nuget.cmake)
include(cmake/validate_d3d_compiler.cmake)
include(cmake/target_prepare_qrc.cmake)
include(cmake/options.cmake)

View File

@@ -37,7 +37,7 @@ Version **1.8.15** was the last that supports older systems
## Third-party
* Qt 5.15.2, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* Qt 6 ([LGPL](http://doc.qt.io/qt-6/lgpl.html)) and Qt 5.15.2 ([LGPL](http://doc.qt.io/qt-5/lgpl.html)) slightly patched
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE))
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))

View File

@@ -5,7 +5,7 @@
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
add_executable(Telegram WIN32 MACOSX_BUNDLE)
init_target(Telegram)
init_non_host_target(Telegram)
add_subdirectory(lib_rpl)
add_subdirectory(lib_crl)
@@ -42,12 +42,16 @@ include(cmake/generate_appdata_changelog.cmake)
if (WIN32)
include(cmake/generate_midl.cmake)
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
generate_midl(Telegram ${src_loc}
platform/win/windows_quiethours.idl
platform/win/windows_toastactivator.idl
)
nuget_add_winrt(Telegram)
endif()
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
set_target_properties(Telegram PROPERTIES AUTOMOC ON)
target_prepare_qrc(Telegram)
target_link_libraries(Telegram
PRIVATE
@@ -111,6 +115,8 @@ PRIVATE
api/api_cloud_password.cpp
api/api_cloud_password.h
api/api_common.h
api/api_confirm_phone.cpp
api/api_confirm_phone.h
api/api_editing.cpp
api/api_editing.h
api/api_global_privacy.cpp
@@ -121,6 +127,10 @@ PRIVATE
api/api_invite_links.h
api/api_media.cpp
api/api_media.h
api/api_peer_photo.cpp
api/api_peer_photo.h
api/api_polls.cpp
api/api_polls.h
api/api_self_destruct.cpp
api/api_self_destruct.h
api/api_send_progress.cpp
@@ -139,6 +149,8 @@ PRIVATE
api/api_updates.h
api/api_user_privacy.cpp
api/api_user_privacy.h
api/api_views.cpp
api/api_views.h
api/api_who_read.cpp
api/api_who_read.h
boxes/filters/edit_filter_box.cpp
@@ -155,20 +167,29 @@ PRIVATE
boxes/peers/edit_participant_box.h
boxes/peers/edit_participants_box.cpp
boxes/peers/edit_participants_box.h
boxes/peers/edit_peer_common.h
boxes/peers/edit_peer_info_box.cpp
boxes/peers/edit_peer_info_box.h
boxes/peers/edit_peer_invite_link.cpp
boxes/peers/edit_peer_invite_link.h
boxes/peers/edit_peer_invite_links.cpp
boxes/peers/edit_peer_invite_links.h
boxes/peers/edit_peer_type_box.cpp
boxes/peers/edit_peer_type_box.h
boxes/peers/edit_peer_history_visibility_box.cpp
boxes/peers/edit_peer_history_visibility_box.h
boxes/peers/edit_peer_permissions_box.cpp
boxes/peers/edit_peer_permissions_box.h
boxes/peers/edit_peer_requests_box.cpp
boxes/peers/edit_peer_requests_box.h
boxes/peers/edit_peer_type_box.cpp
boxes/peers/edit_peer_type_box.h
boxes/peers/peer_short_info_box.cpp
boxes/peers/peer_short_info_box.h
boxes/peers/prepare_short_info_box.cpp
boxes/peers/prepare_short_info_box.h
boxes/about_box.cpp
boxes/about_box.h
boxes/about_sponsored_box.cpp
boxes/about_sponsored_box.h
boxes/abstract_box.cpp
boxes/abstract_box.h
boxes/add_contact_box.cpp
@@ -183,14 +204,12 @@ PRIVATE
boxes/background_preview_box.h
boxes/change_phone_box.cpp
boxes/change_phone_box.h
boxes/confirm_box.cpp
boxes/confirm_box.h
boxes/confirm_phone_box.cpp
boxes/confirm_phone_box.h
boxes/connection_box.cpp
boxes/connection_box.h
boxes/create_poll_box.cpp
boxes/create_poll_box.h
boxes/delete_messages_box.cpp
boxes/delete_messages_box.h
boxes/dictionaries_manager.cpp
boxes/dictionaries_manager.h
boxes/download_path_box.cpp
@@ -205,6 +224,8 @@ PRIVATE
boxes/language_box.h
boxes/local_storage_box.cpp
boxes/local_storage_box.h
boxes/max_invite_box.cpp
boxes/max_invite_box.h
boxes/mute_settings_box.cpp
boxes/mute_settings_box.h
boxes/peer_list_box.cpp
@@ -215,8 +236,10 @@ PRIVATE
boxes/peer_lists_box.h
boxes/passcode_box.cpp
boxes/passcode_box.h
boxes/rate_call_box.cpp
boxes/rate_call_box.h
boxes/phone_banned_box.cpp
boxes/phone_banned_box.h
boxes/pin_messages_box.cpp
boxes/pin_messages_box.h
boxes/self_destruction_box.cpp
boxes/self_destruction_box.h
boxes/send_files_box.cpp
@@ -235,6 +258,8 @@ PRIVATE
boxes/username_box.h
calls/group/calls_choose_join_as.cpp
calls/group/calls_choose_join_as.h
calls/group/calls_cover_item.cpp
calls/group/calls_cover_item.h
calls/group/calls_group_call.cpp
calls/group/calls_group_call.h
calls/group/calls_group_common.cpp
@@ -400,6 +425,7 @@ PRIVATE
data/data_file_origin.cpp
data/data_file_origin.h
data/data_flags.h
data/data_game.cpp
data/data_game.h
data/data_group_call.cpp
data/data_group_call.h
@@ -415,6 +441,7 @@ PRIVATE
data/data_media_types.h
data/data_messages.cpp
data/data_messages.h
data/data_msg_id.h
data/data_notify_settings.cpp
data/data_notify_settings.h
data/data_peer.cpp
@@ -447,6 +474,8 @@ PRIVATE
data/data_shared_media.h
data/data_sparse_ids.cpp
data/data_sparse_ids.h
data/data_sponsored_messages.cpp
data/data_sponsored_messages.h
data/data_streaming.cpp
data/data_streaming.h
data/data_types.cpp
@@ -467,8 +496,6 @@ PRIVATE
dialogs/dialogs_inner_widget.h
dialogs/dialogs_key.cpp
dialogs/dialogs_key.h
dialogs/dialogs_layout.cpp
dialogs/dialogs_layout.h
dialogs/dialogs_list.cpp
dialogs/dialogs_list.h
dialogs/dialogs_main_list.cpp
@@ -481,6 +508,10 @@ PRIVATE
dialogs/dialogs_search_from_controllers.h
dialogs/dialogs_widget.cpp
dialogs/dialogs_widget.h
dialogs/ui/dialogs_layout.cpp
dialogs/ui/dialogs_layout.h
dialogs/ui/dialogs_message_view.cpp
dialogs/ui/dialogs_message_view.h
editor/color_picker.cpp
editor/color_picker.h
editor/controllers/controllers.h
@@ -596,8 +627,8 @@ PRIVATE
history/view/history_view_emoji_interactions.h
history/view/history_view_empty_list_bubble.cpp
history/view/history_view_empty_list_bubble.h
history/view/history_view_group_call_tracker.cpp
history/view/history_view_group_call_tracker.h
history/view/history_view_group_call_bar.cpp
history/view/history_view_group_call_bar.h
history/view/history_view_list_widget.cpp
history/view/history_view_list_widget.h
history/view/history_view_message.cpp
@@ -611,6 +642,8 @@ PRIVATE
history/view/history_view_pinned_tracker.h
history/view/history_view_replies_section.cpp
history/view/history_view_replies_section.h
history/view/history_view_requests_bar.cpp
history/view/history_view_requests_bar.h
history/view/history_view_schedule_box.cpp
history/view/history_view_schedule_box.h
history/view/history_view_scheduled_section.cpp
@@ -621,6 +654,8 @@ PRIVATE
history/view/history_view_service_message.h
history/view/history_view_top_bar_widget.cpp
history/view/history_view_top_bar_widget.h
history/view/history_view_view_button.cpp
history/view/history_view_view_button.h
history/view/history_view_webpage_preview.cpp
history/view/history_view_webpage_preview.h
history/history.cpp
@@ -631,6 +666,10 @@ PRIVATE
history/history_item.h
history/history_item_components.cpp
history/history_item_components.h
history/history_item_edition.cpp
history/history_item_edition.h
history/history_item_reply_markup.cpp
history/history_item_reply_markup.h
history/history_item_text.cpp
history/history_item_text.h
history/history_inner_widget.cpp
@@ -943,6 +982,8 @@ PRIVATE
platform/win/windows_dlls.h
platform/win/windows_event_filter.cpp
platform/win/windows_event_filter.h
platform/win/windows_toast_activator.cpp
platform/win/windows_toast_activator.h
platform/platform_audio.h
platform/platform_file_utilities.h
platform/platform_launcher.h
@@ -1040,6 +1081,8 @@ PRIVATE
ui/chat/attach/attach_item_single_file_preview.h
ui/chat/attach/attach_item_single_media_preview.cpp
ui/chat/attach/attach_item_single_media_preview.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
ui/effects/fireworks_animation.cpp
ui/effects/fireworks_animation.h
ui/effects/round_checkbox.cpp
@@ -1052,10 +1095,6 @@ PRIVATE
ui/image/image_location.h
ui/image/image_location_factory.cpp
ui/image/image_location_factory.h
ui/widgets/continuous_sliders.cpp
ui/widgets/continuous_sliders.h
ui/widgets/discrete_sliders.cpp
ui/widgets/discrete_sliders.h
ui/widgets/level_meter.cpp
ui/widgets/level_meter.h
ui/widgets/multi_select.cpp
@@ -1221,8 +1260,7 @@ elseif (APPLE)
endif()
set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets)
set_target_properties(Telegram PROPERTIES RESOURCE ${icons_path})
target_sources(Telegram PRIVATE ${icons_path})
target_add_resource(Telegram ${icons_path})
set(lang_packs
en
@@ -1243,6 +1281,11 @@ elseif (APPLE)
source_group(TREE ${res_loc} PREFIX Resources FILES ${strings_path})
endforeach()
add_custom_command(TARGET Telegram
PRE_LINK
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
)
if (NOT build_macstore)
add_custom_command(TARGET Telegram
PRE_LINK
@@ -1250,10 +1293,15 @@ elseif (APPLE)
COMMAND cp $<TARGET_FILE:Updater> $<TARGET_FILE_DIR:Telegram>/../Frameworks/
)
if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS)
if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64")
set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}")
else()
set(crashpad_dir_part "")
endif()
add_custom_command(TARGET Telegram
PRE_LINK
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Helpers
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>${crashpad_dir_part}/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
)
endif()
endif()
@@ -1297,6 +1345,8 @@ if (build_macstore)
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Frameworks
COMMAND cp -a ${libs_loc}/breakpad/src/client/mac/build/Release/Breakpad.framework $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework/Resources/crash_report_sender.app
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework/Resources/Inspector
)
else()
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
@@ -1316,7 +1366,6 @@ set_target_properties(Telegram PROPERTIES
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${bundle_identifier}
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${desktop_app_version_string}
XCODE_ATTRIBUTE_PRODUCT_NAME ${output_name}
XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT $<$<NOT:$<CONFIG:Debug>>:dwarf-with-dsym>
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
XCODE_ATTRIBUTE_COMBINE_HIDPI_IMAGES YES
@@ -1368,18 +1417,20 @@ if (WIN32)
/DELAYLOAD:gdiplus.dll
/DELAYLOAD:version.dll
/DELAYLOAD:dwmapi.dll
/DELAYLOAD:uxtheme.dll
/DELAYLOAD:crypt32.dll
/DELAYLOAD:bcrypt.dll
/DELAYLOAD:imm32.dll
/DELAYLOAD:netapi32.dll
/DELAYLOAD:userenv.dll
/DELAYLOAD:wtsapi32.dll
/DELAYLOAD:propsys.dll
)
endif()
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
add_executable(Updater WIN32)
init_target(Updater)
init_non_host_target(Updater)
add_dependencies(Telegram Updater)
@@ -1391,7 +1442,9 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
_other/updater.h
)
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
set_target_properties(Updater PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${output_folder}
)
if (WIN32)
get_filename_component(lib_base_loc lib_base REALPATH)

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

View File

@@ -978,6 +978,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_removed_users" = "Removed users";
"lng_manage_peer_permissions" = "Permissions";
"lng_manage_peer_invite_links" = "Invite links";
"lng_manage_peer_requests" = "Member Requests";
"lng_manage_peer_requests_channel" = "Subscriber Requests";
"lng_manage_peer_group_type" = "Group type";
"lng_manage_peer_channel_type" = "Channel type";
@@ -1158,6 +1160,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_user_left" = "{from} left the group";
"lng_action_user_joined" = "{from} joined the group";
"lng_action_user_joined_by_link" = "{from} joined the group via invite link";
"lng_action_user_joined_by_request" = "{from} was accepted to the group";
"lng_action_you_joined_by_request" = "Your request to join the group was approved";
"lng_action_you_joined_by_request_channel" = "Your request to join the channel was approved";
"lng_action_user_registered" = "{from} just joined Telegram";
"lng_action_removed_photo" = "{from} removed group photo";
"lng_action_removed_photo_channel" = "Channel photo removed";
@@ -1269,6 +1274,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_joined#other" = "{count} joined";
"lng_group_invite_remaining#one" = "{count} remaining";
"lng_group_invite_remaining#other" = "{count} remaining";
"lng_group_invite_requested#one" = "{count} requested";
"lng_group_invite_requested#other" = "{count} requested";
"lng_group_invite_requested_full#one" = "{count} requested to join";
"lng_group_invite_requested_full#other" = "{count} requested to join";
"lng_group_invite_can_join#one" = "{count} can join";
"lng_group_invite_can_join#other" = "{count} can join";
"lng_group_invite_days_left#one" = "{count} day left";
@@ -1298,6 +1307,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_link_expired" = "Expired";
"lng_group_invite_edit_title" = "Edit Link";
"lng_group_invite_new_title" = "New Link";
"lng_group_invite_label_header" = "Link Name (optional)";
"lng_group_invite_label_about" = "Only admins will see this name.";
"lng_group_invite_expire_title" = "Limit by time period";
"lng_group_invite_expire_about" = "You can make the link expire after a certain time.";
"lng_group_invite_expire_never" = "No limit";
@@ -1320,6 +1331,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_qr_title" = "Invite by QR Code";
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
"lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
"lng_group_invite_request_approve" = "Request admin approval";
"lng_group_invite_about_approve" = "New users will be able to join the group only after having been approved by the admins.";
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
"lng_group_invite_about_no_approve_channel" = "New users will be able to join the channel without being approved by the admins.";
"lng_group_request_to_join" = "Request to Join";
"lng_group_request_about" = "This group accepts new members only after they are approved by its admins.";
"lng_group_request_about_channel" = "This channel accepts new subscribers only after they are approved by its admins.";
"lng_group_request_sent" = "You will be added to the group once its admins approve your request.";
"lng_group_request_sent_channel" = "You will be added to the channel once its admins approve your request.";
"lng_group_requests_pending#one" = "{count} user requested to join";
"lng_group_requests_pending#other" = "{count} users requested to join";
"lng_group_requests_pending_user" = "{user} requested to join";
"lng_group_requests_status_today" = "requested to join today at {time}";
"lng_group_requests_status_yesterday" = "requested to join yesterday at {time}";
"lng_group_requests_status_date_time" = "requested to join {date} at {time}";
"lng_group_requests_add" = "Add to Group";
"lng_group_requests_add_channel" = "Add to Channel";
"lng_group_requests_dismiss" = "Dismiss";
"lng_group_requests_was_added" = "{user} has been added to the group.";
"lng_group_requests_was_added_channel" = "{user} has been added to the channel.";
"lng_group_requests_none" = "You have no pending requests\nto join your group.";
"lng_group_requests_none_channel" = "You have no pending requests\nto join your channel.";
"lng_channel_public_link_copied" = "Link copied to clipboard.";
"lng_context_about_private_link" = "This link will only work for members of this chat.";
@@ -1335,6 +1370,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forwarded_imported" = "This message was imported from another app. It may not be real.";
"lng_signed_author" = "Author: {user}";
"lng_in_reply_to" = "In reply to";
"lng_sponsored" = "sponsored";
"lng_edited" = "edited";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
@@ -1508,6 +1544,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
"lng_record_lock_discard" = "Discard";
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
"lng_will_be_notified" = "Members will be notified when you post";
"lng_wont_be_notified" = "Members will not be notified when you post";
"lng_willbe_history" = "Please select a chat to start messaging";
@@ -2448,6 +2485,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_participant_joined_channel" = "{from} joined the channel";
"lng_admin_log_participant_joined_by_link" = "{from} joined the group via {link}";
"lng_admin_log_participant_joined_by_link_channel" = "{from} joined the channel via {link}";
"lng_admin_log_participant_approved_by_link" = "{from} was approved to join the group via {link} by {user}";
"lng_admin_log_participant_approved_by_link_channel" = "{from} was approved to join the channel via {link} by {user}";
"lng_admin_log_revoke_invite_link" = "{from} revoked invite link {link}";
"lng_admin_log_delete_invite_link" = "{from} deleted invite link {link}";
"lng_admin_log_participant_left" = "{from} left the group";
@@ -2492,6 +2531,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_edited_invite_link" = "edited invite link {link}";
"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}";
"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}";
"lng_admin_log_invite_link_label" = "Name: {previous} -> {limit}";
"lng_admin_log_invite_link_request_needed" = "Now admin approval is required to join.";
"lng_admin_log_invite_link_request_not_needed" = "Now admin approval is not required to join.";
"lng_admin_log_restricted_forever" = "indefinitely";
"lng_admin_log_restricted_until" = "until {date}";
"lng_admin_log_banned_view_messages" = "Read messages";
@@ -2871,6 +2913,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
"lng_filters_remove_yes" = "Remove";
"lng_chat_theme_change" = "Change colors";
"lng_chat_theme_none" = "No\nTheme";
"lng_chat_theme_apply" = "Apply Theme";
"lng_chat_theme_reset" = "Reset Theme";
"lng_chat_theme_dont" = "Do Not Set Theme";
"lng_chat_theme_title" = "Select theme";
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
"lng_photo_editor_menu_delete" = "Delete";
"lng_photo_editor_menu_flip" = "Flip";
"lng_photo_editor_menu_duplicate" = "Duplicate";
@@ -2880,6 +2930,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_voice_speed_fast" = "Fast";
"lng_voice_speed_very_fast" = "Very fast";
"lng_view_button_user" = "View user";
"lng_view_button_bot" = "View bot";
"lng_view_button_group" = "View group";
"lng_view_button_channel" = "View channel";
"lng_view_button_background" = "View background";
"lng_view_button_theme" = "View theme";
"lng_view_button_message" = "View message";
"lng_view_button_voice_chat" = "Voice chat";
"lng_view_button_voice_chat_channel" = "Live stream";
"lng_view_button_request_join" = "Request to Join";
"lng_sponsored_title" = "What are sponsored messages?";
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties cant spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
"lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate together.";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

@@ -128,8 +128,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat;
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#4dbdc099 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull;
channelFull#e9b27a17 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull;
chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> = ChatFull;
channelFull#59cff963 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> = ChatFull;
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
@@ -188,6 +188,7 @@ messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector<long> =
messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@@ -378,6 +379,8 @@ updateChannelParticipant#985d3abb flags:# channel_id:long date:int actor_id:long
updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update;
updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@@ -549,10 +552,10 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
chatInviteExported#b18105e8 flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite;
chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
@@ -625,7 +628,7 @@ channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter;
channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector<MessageRange> = ChannelMessagesFilter;
channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant;
channelParticipantSelf#28a8bc67 user_id:long inviter_id:long date:int = ChannelParticipant;
channelParticipantSelf#35a8bfa7 flags:# via_invite:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant;
channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant;
@@ -908,6 +911,7 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit
channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction;
channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction;
channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction;
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
@@ -1122,7 +1126,7 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
inputThemeSlug#f5890df1 slug:string = InputTheme;
theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme;
theme#a00e67d6 flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?Vector<ThemeSettings> emoticon:flags.6?string installs_count:flags.4?int = Theme;
account.themesNotModified#f41eb622 = account.Themes;
account.themes#9a3d8c6d hash:long themes:Vector<Theme> = account.Themes;
@@ -1234,7 +1238,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true
messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector<int> = messages.AffectedFoundMessages;
chatInviteImporter#b5cd5f4 user_id:long date:int = ChatInviteImporter;
chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter;
messages.exportedChatInvites#bdc62dcc count:int invites:Vector<ExportedChatInvite> users:Vector<User> = messages.ExportedChatInvites;
@@ -1271,15 +1275,18 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme;
account.chatThemesNotModified#e011e1c4 = account.ChatThemes;
account.chatThemes#fe4cbebd hash:int themes:Vector<ChatTheme> = account.ChatThemes;
sponsoredMessage#2a3c381f flags:# random_id:bytes from_id:Peer start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
sponsoredMessage#d151e19a flags:# random_id:bytes from_id:Peer channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
messages.sponsoredMessages#65a4c7d5 messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector<SearchResultsCalendarPeriod> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SearchResultsCalendar;
searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosition;
messages.searchResultsPositions#53b22baf count:int positions:Vector<SearchResultsPosition> = messages.SearchResultsPositions;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1366,10 +1373,10 @@ account.resetWallPapers#bb3b9804 = Bool;
account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings;
account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool;
account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;
account.createTheme#8432c21f flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool;
account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool;
account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme;
account.getThemes#7206e458 format:string hash:long = account.Themes;
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
@@ -1380,7 +1387,7 @@ account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = Globa
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
account.resetPassword#9308ce1b = account.ResetPasswordResult;
account.declinePasswordReset#4c9409f6 = Bool;
account.getChatThemes#d6d71d7b hash:int = account.ChatThemes;
account.getChatThemes#d638de89 hash:long = account.Themes;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
@@ -1412,7 +1419,7 @@ messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags
messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory;
messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
@@ -1444,7 +1451,7 @@ messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages
messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers;
messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers;
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite;
messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite;
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
messages.importChatInvite#6c50051c hash:string = Updates;
messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
@@ -1508,7 +1515,6 @@ messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
messages.getStatsURL#812c2ae6 flags:# dark:flags.0?true peer:InputPeer params:string = StatsURL;
messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
@@ -1542,15 +1548,18 @@ messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:st
messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool;
messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite;
messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite;
messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool;
messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool;
messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites;
messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates;
messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer;
messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates;
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar;
messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions;
messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1698,4 +1707,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 133
// LAYER 134

View File

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

View File

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

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,1,2,0
PRODUCTVERSION 3,1,2,0
FILEVERSION 3,2,0,0
PRODUCTVERSION 3,2,0,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", "3.1.2.0"
VALUE "FileVersion", "3.2.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.1.2.0"
VALUE "ProductVersion", "3.2.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -161,6 +161,7 @@ int main(int argc, char *argv[])
QString remove;
int version = 0;
[[maybe_unused]] bool targetwin64 = false;
[[maybe_unused]] bool targetarmac = false;
QFileInfoList files;
for (int i = 0; i < argc; ++i) {
if (string("-path") == argv[i] && i + 1 < argc) {
@@ -170,6 +171,12 @@ int main(int argc, char *argv[])
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
} else if (string("-target") == argv[i] && i + 1 < argc) {
targetwin64 = (string("win64") == argv[i + 1]);
} else if (string("-arch") == argv[i] && i + 1 < argc) {
targetarmac = (string("arm64") == argv[i + 1]);
if (!targetarmac && string("x86_64") != argv[i + 1]) {
cout << "Bad -arch param value passed: " << argv[i + 1] << "\n";
return -1;
}
} else if (string("-version") == argv[i] && i + 1 < argc) {
version = QString(argv[i + 1]).toInt();
} else if (string("-beta") == argv[i]) {
@@ -494,7 +501,7 @@ int main(int argc, char *argv[])
#ifdef Q_OS_WIN
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_MAC
QString outName(QString("tmacupd%1").arg(AlphaVersion ? AlphaVersion : version));
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_UNIX
QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version));
#else

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_attached_stickers.h"
#include "apiwrap.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/stickers_box.h"
#include "data/data_document.h"
@@ -37,7 +37,7 @@ void AttachedStickers::request(
}
if (result.v.isEmpty()) {
strongController->show(
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
return;
} else if (result.v.size() > 1) {
strongController->show(
@@ -63,7 +63,7 @@ void AttachedStickers::request(
_requestId = 0;
if (const auto strongController = weak.get()) {
strongController->show(
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
}
}).send();
}

View File

@@ -82,7 +82,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
&& lastDate.weekNumber() == nowDate.weekNumber()) {
result.active = langDayOfWeek(lastDate);
} else {
result.active = lastDate.toString(qsl("d.MM.yy"));
result.active = lastDate.toString(cDateFormat());
}
}

View File

@@ -26,7 +26,7 @@ BlockedPeers::Slice TLToSlice(
Data::Session &owner) {
const auto create = [&](int count, const QVector<MTPPeerBlocked> &list) {
auto slice = BlockedPeers::Slice();
slice.total = std::max(count, list.size());
slice.total = std::max(count, int(list.size()));
slice.list.reserve(list.size());
for (const auto &contact : list) {
contact.match([&](const MTPDpeerBlocked &data) {

View File

@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_cloud_password.h"
#include "core/core_cloud_password.h"
#include "api/api_send_progress.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h"
#include "boxes/passcode_box.h"
#include "lang/lang_keys.h"
@@ -91,7 +91,7 @@ void SendBotCallbackData(
result.match([&](const MTPDmessages_botCallbackAnswer &data) {
if (const auto message = data.vmessage()) {
if (data.is_alert()) {
Ui::show(Box<InformBox>(qs(*message)));
Ui::show(Box<Ui::InformBox>(qs(*message)));
} else {
if (withPassword) {
Ui::hideLayer();

View File

@@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/abstract_box.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@@ -38,11 +38,12 @@ void CheckChatInvite(
if (!strongController) {
return;
}
const auto isGroup = !data.is_broadcast();
const auto box = strongController->show(Box<ConfirmInviteBox>(
session,
data,
invitePeekChannel,
[=] { session->api().importChatInvite(hash); }));
[=] { session->api().importChatInvite(hash, isGroup); }));
if (invitePeekChannel) {
box->boxClosing(
) | rpl::filter([=] {
@@ -86,7 +87,7 @@ void CheckChatInvite(
Core::App().hideMediaView();
if (const auto strong = weak.get()) {
strong->show(
Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
Box<Ui::InformBox>(tr::lng_group_invite_bad_link(tr::now)));
}
});
}
@@ -103,8 +104,11 @@ ConfirmInviteBox::ConfirmInviteBox(
, _submit(std::move(submit))
, _title(this, st::confirmInviteTitle)
, _status(this, st::confirmInviteStatus)
, _about(this, st::confirmInviteAbout)
, _aboutRequests(this, st::confirmInviteStatus)
, _participants(GetParticipants(_session, data))
, _isChannel(data.is_channel() && !data.is_megagroup()) {
, _isChannel(data.is_channel() && !data.is_megagroup())
, _requestApprove(data.is_request_needed()) {
const auto title = qs(data.vtitle());
const auto count = data.vparticipants_count().v;
const auto status = [&] {
@@ -125,6 +129,18 @@ ConfirmInviteBox::ConfirmInviteBox(
}();
_title->setText(title);
_status->setText(status);
if (const auto v = qs(data.vabout().value_or_empty()); !v.isEmpty()) {
_about->setText(v);
} else {
_about.destroy();
}
if (_requestApprove) {
_aboutRequests->setText(_isChannel
? tr::lng_group_request_about_channel(tr::now)
: tr::lng_group_request_about(tr::now));
} else {
_aboutRequests.destroy();
}
const auto photo = _session->data().processPhoto(data.vphoto());
if (!photo->isNull()) {
@@ -166,7 +182,9 @@ auto ConfirmInviteBox::GetParticipants(
void ConfirmInviteBox::prepare() {
addButton(
(_isChannel
(_requestApprove
? tr::lng_group_request_to_join()
: _isChannel
? tr::lng_profile_join_channel()
: tr::lng_profile_join_group()),
_submit);
@@ -178,7 +196,7 @@ void ConfirmInviteBox::prepare() {
auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom();
if (!_participants.empty()) {
int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
int padding = skip / 2;
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
int sumWidth = _participants.size() * _userWidth;
@@ -195,6 +213,16 @@ void ConfirmInviteBox::prepare() {
newHeight += st::confirmInviteUserHeight;
}
if (_about) {
const auto padding = st::confirmInviteAboutPadding;
_about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
newHeight += padding.top() + _about->height() + padding.bottom();
}
if (_aboutRequests) {
const auto padding = st::confirmInviteAboutRequestsPadding;
_aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
newHeight += padding.top() + _aboutRequests->height() + padding.bottom();
}
setDimensions(st::boxWideWidth, newHeight);
}
@@ -202,6 +230,19 @@ void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop);
_status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop);
auto bottom = _status->y()
+ _status->height()
+ st::boxPadding.bottom()
+ (_participants.empty() ? 0 : st::confirmInviteUserHeight);
if (_about) {
const auto padding = st::confirmInviteAboutPadding;
_about->move((width() - _about->width()) / 2, bottom + padding.top());
bottom += padding.top() + _about->height() + padding.bottom();
}
if (_aboutRequests) {
const auto padding = st::confirmInviteAboutRequestsPadding;
_aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top());
}
}
void ConfirmInviteBox::paintEvent(QPaintEvent *e) {

View File

@@ -68,10 +68,13 @@ private:
Fn<void()> _submit;
object_ptr<Ui::FlatLabel> _title;
object_ptr<Ui::FlatLabel> _status;
object_ptr<Ui::FlatLabel> _about;
object_ptr<Ui::FlatLabel> _aboutRequests;
std::shared_ptr<Data::PhotoMedia> _photo;
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
std::vector<Participant> _participants;
bool _isChannel = false;
bool _requestApprove = false;
int _userWidth = 0;

View File

@@ -0,0 +1,125 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_confirm_phone.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/confirm_phone_box.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "window/window_session_controller.h"
namespace Api {
ConfirmPhone::ConfirmPhone(not_null<ApiWrap*> api)
: _api(&api->instance()) {
}
void ConfirmPhone::resolve(
not_null<Window::SessionController*> controller,
const QString &phone,
const QString &hash) {
if (_sendRequestId) {
return;
}
_sendRequestId = _api.request(MTPaccount_SendConfirmPhoneCode(
MTP_string(hash),
MTP_codeSettings(MTP_flags(0))
)).done([=](const MTPauth_SentCode &result) {
_sendRequestId = 0;
result.match([&](const MTPDauth_sentCode &data) {
const auto sentCodeLength = data.vtype().match([&](
const MTPDauth_sentCodeTypeApp &data) {
LOG(("Error: should not be in-app code!"));
return 0;
}, [&](const MTPDauth_sentCodeTypeSms &data) {
return data.vlength().v;
}, [&](const MTPDauth_sentCodeTypeCall &data) {
return data.vlength().v;
}, [&](const MTPDauth_sentCodeTypeFlashCall &data) {
LOG(("Error: should not be flashcall!"));
return 0;
});
const auto phoneHash = qs(data.vphone_code_hash());
const auto timeout = [&]() -> std::optional<int> {
if (const auto nextType = data.vnext_type()) {
if (nextType->type() == mtpc_auth_codeTypeCall) {
return data.vtimeout().value_or(60);
}
}
return std::nullopt;
}();
auto box = Box<Ui::ConfirmPhoneBox>(
phone,
sentCodeLength,
timeout);
const auto boxWeak = Ui::MakeWeak(box.data());
box->resendRequests(
) | rpl::start_with_next([=] {
_api.request(MTPauth_ResendCode(
MTP_string(phone),
MTP_string(phoneHash)
)).done([=](const MTPauth_SentCode &result) {
if (boxWeak) {
boxWeak->callDone();
}
}).send();
}, box->lifetime());
box->checkRequests(
) | rpl::start_with_next([=](const QString &code) {
if (_checkRequestId) {
return;
}
_checkRequestId = _api.request(MTPaccount_ConfirmPhone(
MTP_string(phoneHash),
MTP_string(code)
)).done([=](const MTPBool &result) {
_checkRequestId = 0;
controller->show(
Box<Ui::InformBox>(
tr::lng_confirm_phone_success(
tr::now,
lt_phone,
Ui::FormatPhone(phone))),
Ui::LayerOption::CloseOther);
}).fail([=](const MTP::Error &error) {
_checkRequestId = 0;
if (!boxWeak) {
return;
}
const auto errorText = MTP::IsFloodError(error)
? tr::lng_flood_error(tr::now)
: (error.type() == (u"PHONE_CODE_EMPTY"_q)
|| error.type() == (u"PHONE_CODE_INVALID"_q))
? tr::lng_bad_code(tr::now)
: Lang::Hard::ServerError();
boxWeak->showServerError(errorText);
}).handleFloodErrors().send();
}, box->lifetime());
controller->show(std::move(box), Ui::LayerOption::CloseOther);
});
}).fail([=](const MTP::Error &error) {
_sendRequestId = 0;
_checkRequestId = 0;
const auto errorText = MTP::IsFloodError(error)
? tr::lng_flood_error(tr::now)
: (error.code() == 400)
? tr::lng_confirm_phone_link_invalid(tr::now)
: Lang::Hard::ServerError();
controller->show(
Box<Ui::InformBox>(errorText),
Ui::LayerOption::CloseOther);
}).handleFloodErrors().send();
}
} // namespace Api

View File

@@ -0,0 +1,36 @@
/*
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
#include "mtproto/sender.h"
class ApiWrap;
namespace Window {
class SessionController;
} // namespace Window
namespace Api {
class ConfirmPhone final {
public:
explicit ConfirmPhone(not_null<ApiWrap*> api);
void resolve(
not_null<Window::SessionController*> controller,
const QString &phone,
const QString &hash);
private:
MTP::Sender _api;
mtpRequestId _sendRequestId = 0;
mtpRequestId _checkRequestId = 0;
};
} // namespace Api

View File

@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_media.h"
#include "api/api_text_entities.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
#include "history/history.h"
@@ -149,7 +149,8 @@ void EditMessageWithUploadedMedia(
session->data().sendHistoryChangeNotifications();
if (mediaInvalid) {
Ui::show(
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
Box<Ui::InformBox>(
tr::lng_edit_media_invalid_file(tr::now)),
Ui::LayerOption::KeepOther);
}
} else {

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_changes.h"
#include "main/main_session.h"
#include "base/unixtime.h"
#include "apiwrap.h"
namespace Api {
@@ -69,17 +70,28 @@ InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
void InviteLinks::create(
not_null<PeerData*> peer,
Fn<void(Link)> done,
const QString &label,
TimeId expireDate,
int usageLimit) {
performCreate(peer, std::move(done), false, expireDate, usageLimit);
int usageLimit,
bool requestApproval) {
performCreate(
peer,
std::move(done),
false,
label,
expireDate,
usageLimit,
requestApproval);
}
void InviteLinks::performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
const QString &label,
TimeId expireDate,
int usageLimit) {
int usageLimit,
bool requestApproval) {
if (const auto i = _createCallbacks.find(peer)
; i != end(_createCallbacks)) {
if (done) {
@@ -97,11 +109,16 @@ void InviteLinks::performCreate(
MTP_flags((revokeLegacyPermanent
? Flag::f_legacy_revoke_permanent
: Flag(0))
| (!label.isEmpty() ? Flag::f_title : Flag(0))
| (expireDate ? Flag::f_expire_date : Flag(0))
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
| ((!requestApproval && usageLimit)
? Flag::f_usage_limit
: Flag(0))
| (requestApproval ? Flag::f_request_needed : Flag(0))),
peer->input,
MTP_int(expireDate),
MTP_int(usageLimit)
MTP_int(usageLimit),
MTP_string(label)
)).done([=](const MTPExportedChatInvite &result) {
const auto callbacks = _createCallbacks.take(peer);
const auto link = prepend(peer, peer->session().user(), result);
@@ -199,8 +216,10 @@ void InviteLinks::edit(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link,
const QString &label,
TimeId expireDate,
int usageLimit,
bool requestApproval,
Fn<void(Link)> done) {
performEdit(
peer,
@@ -208,8 +227,10 @@ void InviteLinks::edit(
link,
std::move(done),
false,
label,
expireDate,
usageLimit);
usageLimit,
requestApproval);
}
void InviteLinks::performEdit(
@@ -218,8 +239,10 @@ void InviteLinks::performEdit(
const QString &link,
Fn<void(Link)> done,
bool revoke,
const QString &label,
TimeId expireDate,
int usageLimit) {
int usageLimit,
bool requestApproval) {
const auto key = LinkKey{ peer, link };
if (_deleteCallbacks.contains(key)) {
return;
@@ -236,14 +259,21 @@ void InviteLinks::performEdit(
callbacks.push_back(std::move(done));
}
using Flag = MTPmessages_EditExportedChatInvite::Flag;
const auto flags = (revoke ? Flag::f_revoked : Flag(0))
| (!revoke ? Flag::f_title : Flag(0))
| (!revoke ? Flag::f_expire_date : Flag(0))
| ((!revoke && !requestApproval) ? Flag::f_usage_limit : Flag(0))
| ((!revoke && (requestApproval || !usageLimit))
? Flag::f_request_needed
: Flag(0));
_api->request(MTPmessages_EditExportedChatInvite(
MTP_flags((revoke ? Flag::f_revoked : Flag(0))
| (!revoke ? Flag::f_expire_date : Flag(0))
| (!revoke ? Flag::f_usage_limit : Flag(0))),
MTP_flags(flags),
peer->input,
MTP_string(link),
MTP_int(expireDate),
MTP_int(usageLimit)
MTP_int(usageLimit),
MTP_bool(requestApproval),
MTP_string(label)
)).done([=](const MTPmessages_ExportedChatInvite &result) {
const auto callbacks = _editCallbacks.take(key);
const auto peer = key.peer;
@@ -421,6 +451,88 @@ void InviteLinks::requestMyLinks(not_null<PeerData*> peer) {
_firstSliceRequests.emplace(peer, requestId);
}
void InviteLinks::processRequest(
not_null<PeerData*> peer,
const QString &link,
not_null<UserData*> user,
bool approved,
Fn<void()> done,
Fn<void()> fail) {
if (_processRequests.contains({ peer, user })) {
return;
}
_processRequests.emplace(
std::pair{ peer, user },
ProcessRequest{ std::move(done), std::move(fail) });
using Flag = MTPmessages_HideChatJoinRequest::Flag;
_api->request(MTPmessages_HideChatJoinRequest(
MTP_flags(approved ? Flag::f_approved : Flag(0)),
peer->input,
user->inputUser
)).done([=](const MTPUpdates &result) {
if (const auto chat = peer->asChat()) {
if (chat->count > 0) {
if (chat->participants.size() >= chat->count) {
chat->participants.emplace(user);
}
++chat->count;
}
} else if (const auto channel = peer->asChannel()) {
_api->requestParticipantsCountDelayed(channel);
}
_api->applyUpdates(result);
if (link.isEmpty() && approved) {
// We don't know the link that was used for this user.
// Prune all the cache.
for (auto i = begin(_firstJoined); i != end(_firstJoined);) {
if (i->first.peer == peer) {
i = _firstJoined.erase(i);
} else {
++i;
}
}
_firstSlices.remove(peer);
} else if (approved) {
const auto i = _firstJoined.find({ peer, link });
if (i != end(_firstJoined)) {
++i->second.count;
i->second.users.insert(
begin(i->second.users),
JoinedByLinkUser{ user, base::unixtime::now() });
}
}
if (const auto callbacks = _processRequests.take({ peer, user })) {
if (const auto &done = callbacks->done) {
done();
}
}
}).fail([=](const MTP::Error &error) {
if (const auto callbacks = _processRequests.take({ peer, user })) {
if (const auto &fail = callbacks->fail) {
fail();
}
}
}).send();
}
void InviteLinks::applyExternalUpdate(
not_null<PeerData*> peer,
InviteLink updated) {
if (const auto i = _firstSlices.find(peer); i != end(_firstSlices)) {
for (auto &link : i->second.links) {
if (link.link == updated.link) {
link = updated;
}
}
}
_updates.fire({
.peer = peer,
.admin = updated.admin,
.was = updated.link,
.now = updated,
});
}
std::optional<JoinedByLinkSlice> InviteLinks::lookupJoinedFirstSlice(
LinkKey key) const {
const auto i = _firstJoined.find(key);
@@ -484,8 +596,10 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
return;
}
const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
MTP_flags(MTPmessages_GetChatInviteImporters::Flag::f_link),
key.peer->input,
MTP_string(key.link),
MTPstring(), // q
MTP_int(0), // offset_date
MTP_inputUserEmpty(), // offset_user
MTP_int(kJoinedFirstPage)
@@ -623,12 +737,15 @@ auto InviteLinks::parse(
return invite.match([&](const MTPDchatInviteExported &data) {
return Link{
.link = qs(data.vlink()),
.label = qs(data.vtitle().value_or_empty()),
.admin = peer->session().data().user(data.vadmin_id()),
.date = data.vdate().v,
.startDate = data.vstart_date().value_or_empty(),
.expireDate = data.vexpire_date().value_or_empty(),
.usageLimit = data.vusage_limit().value_or_empty(),
.usage = data.vusage().value_or_empty(),
.requested = data.vrequested().value_or_empty(),
.requestApproval = data.is_request_needed(),
.permanent = data.is_permanent(),
.revoked = data.is_revoked(),
};

View File

@@ -13,12 +13,15 @@ namespace Api {
struct InviteLink {
QString link;
QString label;
not_null<UserData*> admin;
TimeId date = 0;
TimeId startDate = 0;
TimeId expireDate = 0;
int usageLimit = 0;
int usage = 0;
int requested = 0;
bool requestApproval = false;
bool permanent = false;
bool revoked = false;
};
@@ -60,14 +63,18 @@ public:
void create(
not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr,
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0);
int usageLimit = 0,
bool requestApproval = false);
void edit(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link,
const QString &label,
TimeId expireDate,
int usageLimit,
bool requestApproval,
Fn<void(Link)> done = nullptr);
void revoke(
not_null<PeerData*> peer,
@@ -97,6 +104,15 @@ public:
void requestMyLinks(not_null<PeerData*> peer);
[[nodiscard]] const Links &myLinks(not_null<PeerData*> peer) const;
void processRequest(
not_null<PeerData*> peer,
const QString &link,
not_null<UserData*> user,
bool approved,
Fn<void()> done,
Fn<void()> fail);
void applyExternalUpdate(not_null<PeerData*> peer, InviteLink updated);
[[nodiscard]] rpl::producer<JoinedByLinkSlice> joinedFirstSliceValue(
not_null<PeerData*> peer,
const QString &link,
@@ -133,6 +149,10 @@ private:
return (a.peer == b.peer) && (a.link == b.link);
}
};
struct ProcessRequest {
Fn<void()> done;
Fn<void()> fail;
};
[[nodiscard]] Links parseSlice(
not_null<PeerData*> peer,
@@ -163,14 +183,18 @@ private:
const QString &link,
Fn<void(Link)> done,
bool revoke,
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0);
int usageLimit = 0,
bool requestApproval = false);
void performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
const QString &label = QString(),
TimeId expireDate = 0,
int usageLimit = 0);
int usageLimit = 0,
bool requestApproval = false);
void requestJoinedFirstSlice(LinkKey key);
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(
@@ -194,6 +218,10 @@ private:
not_null<PeerData*>,
std::vector<Fn<void()>>> _deleteRevokedCallbacks;
base::flat_map<
std::pair<not_null<PeerData*>, not_null<UserData*>>,
ProcessRequest> _processRequests;
rpl::event_stream<Update> _updates;
struct AllRevokedDestroyed {

View File

@@ -0,0 +1,212 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_peer_photo.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "main/main_session.h"
#include "storage/file_upload.h"
#include "storage/localimageloader.h"
#include "storage/storage_user_photos.h"
#include <QtCore/QBuffer>
namespace Api {
namespace {
SendMediaReady PreparePeerPhoto(
MTP::DcId dcId,
PeerId peerId,
QImage &&image) {
PreparedPhotoThumbs photoThumbs;
QVector<MTPPhotoSize> photoSizes;
QByteArray jpeg;
QBuffer jpegBuffer(&jpeg);
image.save(&jpegBuffer, "JPG", 87);
const auto scaled = [&](int size) {
return image.scaled(
size,
size,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
};
const auto push = [&](
const char *type,
QImage &&image,
QByteArray bytes = QByteArray()) {
photoSizes.push_back(MTP_photoSize(
MTP_string(type),
MTP_int(image.width()),
MTP_int(image.height()), MTP_int(0)));
photoThumbs.emplace(type[0], PreparedPhotoThumb{
.image = std::move(image),
.bytes = std::move(bytes)
});
};
push("a", scaled(160));
push("b", scaled(320));
push("c", std::move(image), jpeg);
const auto id = base::RandomValue<PhotoId>();
const auto photo = MTP_photo(
MTP_flags(0),
MTP_long(id),
MTP_long(0),
MTP_bytes(),
MTP_int(base::unixtime::now()),
MTP_vector<MTPPhotoSize>(photoSizes),
MTPVector<MTPVideoSize>(),
MTP_int(dcId));
QString file, filename;
int32 filesize = 0;
QByteArray data;
return SendMediaReady(
SendMediaType::Photo,
file,
filename,
filesize,
data,
id,
id,
u"jpg"_q,
peerId,
photo,
photoThumbs,
MTP_documentEmpty(MTP_long(0)),
jpeg,
0);
}
} // namespace
PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
crl::on_main(_session, [=] {
// You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet.
_session->uploader().photoReady(
) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
ready(data.fullId, data.file);
}, _session->lifetime());
});
}
void PeerPhoto::upload(not_null<PeerData*> peer, QImage &&image) {
peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto(
_api.instance().mainDcId(),
peer->id,
std::move(image));
const auto fakeId = FullMsgId(
peerToChannel(peer->id),
_session->data().nextLocalMessageId());
const auto already = ranges::find(
_uploads,
peer,
[](const auto &pair) { return pair.second; });
if (already != end(_uploads)) {
_session->uploader().cancel(already->first);
_uploads.erase(already);
}
_uploads.emplace(fakeId, peer);
_session->uploader().uploadMedia(fakeId, ready);
}
void PeerPhoto::clear(not_null<PhotoData*> photo) {
const auto self = _session->user();
if (self->userpicPhotoId() == photo->id) {
_api.request(MTPphotos_UpdateProfilePhoto(
MTP_inputPhotoEmpty()
)).done([=](const MTPphotos_Photo &result) {
self->setPhoto(MTP_userProfilePhotoEmpty());
}).send();
} else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) {
const auto applier = [=](const MTPUpdates &result) {
_session->updates().applyUpdates(result);
};
if (const auto chat = photo->peer->asChat()) {
_api.request(MTPmessages_EditChatPhoto(
chat->inputChat,
MTP_inputChatPhotoEmpty()
)).done(applier).send();
} else if (const auto channel = photo->peer->asChannel()) {
_api.request(MTPchannels_EditPhoto(
channel->inputChannel,
MTP_inputChatPhotoEmpty()
)).done(applier).send();
}
} else {
_api.request(MTPphotos_DeletePhotos(
MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
)).send();
_session->storage().remove(Storage::UserPhotosRemoveOne(
peerToUser(self->id),
photo->id));
}
}
void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
const auto maybePeer = _uploads.take(msgId);
if (!maybePeer) {
return;
}
const auto peer = *maybePeer;
const auto applier = [=](const MTPUpdates &result) {
_session->updates().applyUpdates(result);
};
if (peer->isSelf()) {
_api.request(MTPphotos_UploadProfilePhoto(
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble() // video_start_ts
)).done([=](const MTPphotos_Photo &result) {
result.match([&](const MTPDphotos_photo &data) {
_session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers());
});
}).send();
} else if (const auto chat = peer->asChat()) {
const auto history = _session->data().history(chat);
history->sendRequestId = _api.request(MTPmessages_EditChatPhoto(
chat->inputChat,
MTP_inputChatUploadedPhoto(
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble()) // video_start_ts
)).done(applier).afterRequest(history->sendRequestId).send();
} else if (const auto channel = peer->asChannel()) {
const auto history = _session->data().history(channel);
history->sendRequestId = _api.request(MTPchannels_EditPhoto(
channel->inputChannel,
MTP_inputChatUploadedPhoto(
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble()) // video_start_ts
)).done(applier).afterRequest(history->sendRequestId).send();
}
}
} // namespace Api

View File

@@ -0,0 +1,38 @@
/*
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
#include "mtproto/sender.h"
class ApiWrap;
class PeerData;
namespace Main {
class Session;
} // namespace Main
namespace Api {
class PeerPhoto final {
public:
explicit PeerPhoto(not_null<ApiWrap*> api);
void upload(not_null<PeerData*> peer, QImage &&image);
void clear(not_null<PhotoData*> photo);
private:
void ready(const FullMsgId &msgId, const MTPInputFile &file);
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::flat_map<FullMsgId, not_null<PeerData*>> _uploads;
};
} // namespace Api

View File

@@ -0,0 +1,201 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_polls.h"
#include "api/api_common.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "base/random.h"
#include "data/data_changes.h"
#include "data/data_histories.h"
#include "data/data_poll.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_message.h" // ShouldSendSilent
#include "main/main_session.h"
namespace Api {
namespace {
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
} // namespace
Polls::Polls(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance()) {
}
void Polls::create(
const PollData &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail) {
_session->api().sendAction(action);
const auto history = action.history;
const auto peer = history->peer;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
const auto clearCloudDraft = action.clearDraft;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft();
history->clearCloudDraft();
history->startSavingCloudDraft();
}
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto replyTo = action.replyTo;
history->sendRequestId = _api.request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(base::RandomValue<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled)
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) mutable {
_session->updates().applyUpdates(result);
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
done();
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) mutable {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
fail(error);
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
}
void Polls::sendVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options) {
if (_pollVotesRequestIds.contains(itemId)) {
return;
}
const auto item = _session->data().message(itemId);
const auto media = item ? item->media() : nullptr;
const auto poll = media ? media->poll() : nullptr;
if (!item) {
return;
}
const auto showSending = poll && !options.empty();
const auto hideSending = [=] {
if (showSending) {
if (const auto item = _session->data().message(itemId)) {
poll->sendingVotes.clear();
_session->data().requestItemRepaint(item);
}
}
};
if (showSending) {
poll->sendingVotes = options;
_session->data().requestItemRepaint(item);
}
auto prepared = QVector<MTPbytes>();
prepared.reserve(options.size());
ranges::transform(
options,
ranges::back_inserter(prepared),
[](const QByteArray &option) { return MTP_bytes(option); });
const auto requestId = _api.request(MTPmessages_SendVote(
item->history()->peer->input,
MTP_int(item->id),
MTP_vector<MTPbytes>(prepared)
)).done([=](const MTPUpdates &result) {
_pollVotesRequestIds.erase(itemId);
hideSending();
_session->updates().applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollVotesRequestIds.erase(itemId);
hideSending();
}).send();
_pollVotesRequestIds.emplace(itemId, requestId);
}
void Polls::close(not_null<HistoryItem*> item) {
const auto itemId = item->fullId();
if (_pollCloseRequestIds.contains(itemId)) {
return;
}
const auto media = item ? item->media() : nullptr;
const auto poll = media ? media->poll() : nullptr;
if (!poll) {
return;
}
const auto requestId = _api.request(MTPmessages_EditMessage(
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
item->history()->peer->input,
MTP_int(item->id),
MTPstring(),
PollDataToInputMedia(poll, true),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date
)).done([=](const MTPUpdates &result) {
_pollCloseRequestIds.erase(itemId);
_session->updates().applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollCloseRequestIds.erase(itemId);
}).send();
_pollCloseRequestIds.emplace(itemId, requestId);
}
void Polls::reloadResults(not_null<HistoryItem*> item) {
const auto itemId = item->fullId();
if (!IsServerMsgId(item->id)
|| _pollReloadRequestIds.contains(itemId)) {
return;
}
const auto requestId = _api.request(MTPmessages_GetPollResults(
item->history()->peer->input,
MTP_int(item->id)
)).done([=](const MTPUpdates &result) {
_pollReloadRequestIds.erase(itemId);
_session->updates().applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollReloadRequestIds.erase(itemId);
}).send();
_pollReloadRequestIds.emplace(itemId, requestId);
}
} // namespace Api

View File

@@ -0,0 +1,49 @@
/*
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
#include "mtproto/sender.h"
class ApiWrap;
class HistoryItem;
struct PollData;
namespace Main {
class Session;
} // namespace Main
namespace Api {
struct SendAction;
class Polls final {
public:
explicit Polls(not_null<ApiWrap*> api);
void create(
const PollData &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail);
void sendVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options);
void close(not_null<HistoryItem*> item);
void reloadResults(not_null<HistoryItem*> item);
private:
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
};
} // namespace Api

View File

@@ -128,7 +128,7 @@ void SendExistingMedia(
messagePostAuthor,
media,
caption,
MTPReplyMarkup());
HistoryMessageMarkupData());
auto performRequest = [=](const auto &repeatRequest) -> void {
auto &histories = history->owner().histories();
@@ -212,7 +212,7 @@ void SendExistingPhoto(
}
bool SendDice(Api::MessageToSend &message) {
const auto full = message.textWithTags.text.midRef(0).trimmed();
const auto full = QStringView(message.textWithTags.text).trimmed();
auto length = 0;
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
|| length != full.size()) {
@@ -288,7 +288,7 @@ bool SendDice(Api::MessageToSend &message) {
messagePostAuthor,
TextWithEntities(),
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
MTPReplyMarkup());
HistoryMessageMarkupData());
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
@@ -335,7 +335,7 @@ void SendConfirmedFile(
isEditing
? file->to.replaceMediaOf
: session->data().nextLocalMessageId());
auto groupId = file->album ? file->album->groupId : uint64(0);
const auto groupId = file->album ? file->album->groupId : uint64(0);
if (file->album) {
const auto proj = [](const SendingAlbum::Item &item) {
return item.taskId;
@@ -370,13 +370,6 @@ void SendConfirmedFile(
session->user()).flags;
TextUtilities::PrepareForSending(caption, prepareFlags);
TextUtilities::Trim(caption);
auto localEntities = Api::EntitiesToMTP(session, caption.entities);
if (itemToEdit) {
if (const auto id = itemToEdit->groupId()) {
groupId = id.value;
}
}
auto flags = isEditing ? MessageFlags() : NewMessageFlags(peer);
if (file->to.replyTo) {
@@ -408,7 +401,7 @@ void SendConfirmedFile(
? session->user()->name
: QString();
const auto media = [&] {
const auto media = MTPMessageMedia([&] {
if (file->type == SendMediaType::Photo) {
return MTP_messageMediaPhoto(
MTP_flags(MTPDmessageMediaPhoto::Flag::f_photo),
@@ -427,38 +420,21 @@ void SendConfirmedFile(
} else {
Unexpected("Type in sendFilesConfirmed.");
}
}();
}());
if (itemToEdit) {
itemToEdit->savePreviousMedia();
itemToEdit->applyEdition(MTP_message(
MTP_flags(MTPDmessage::Flag::f_media
| ((flags & MessageFlag::HideEdited)
? MTPDmessage::Flag::f_edit_hide
: MTPDmessage::Flag())
| (localEntities.v.isEmpty()
? MTPDmessage::Flag()
: MTPDmessage::Flag::f_entities)),
MTP_int(newId.msg),
peerToMTP(messageFromId),
peerToMTP(file->to.peer),
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
replyHeader,
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
media,
MTPReplyMarkup(),
localEntities,
MTPint(), // views
MTPint(), // forwards
MTPMessageReplies(),
MTPint(), // edit_date
MTP_string(messagePostAuthor),
MTP_long(groupId),
//MTPMessageReactions(),
MTPVector<MTPRestrictionReason>(),
MTPint()).c_message());
auto edition = HistoryMessageEdition();
edition.isEditHide = (flags & MessageFlag::HideEdited);
edition.editDate = 0;
edition.views = 0;
edition.forwards = 0;
edition.ttl = 0;
edition.mtpMedia = &media;
edition.textWithEntities = caption;
edition.useSameMarkup = true;
edition.useSameReplies = true;
itemToEdit->applyEdition(std::move(edition));
} else {
const auto viaBotId = UserId();
history->addNewLocalMessage(
@@ -471,7 +447,7 @@ void SendConfirmedFile(
messagePostAuthor,
caption,
media,
MTPReplyMarkup(),
HistoryMessageMarkupData(),
groupId);
}

View File

@@ -42,7 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "apiwrap.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "app.h" // App::quitting
@@ -239,11 +239,12 @@ Updates::Updates(not_null<Main::Session*> session)
}).send();
using namespace rpl::mappers;
base::ObservableViewer(
api().fullPeerUpdated()
) | rpl::filter([](not_null<PeerData*> peer) {
return peer->isChat() || peer->isMegagroup();
}) | rpl::start_with_next([=](not_null<PeerData*> peer) {
session->changes().peerUpdates(
Data::PeerUpdate::Flag::FullInfo
) | rpl::filter([](const Data::PeerUpdate &update) {
return update.peer->isChat() || update.peer->isMegagroup();
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
const auto peer = update.peer;
if (const auto list = _pendingSpeakingCallParticipants.take(peer)) {
if (const auto call = peer->groupCall()) {
for (const auto &[participantPeerId, when] : *list) {
@@ -1965,6 +1966,19 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
} break;
case mtpc_updatePendingJoinRequests: {
const auto &d = update.c_updatePendingJoinRequests();
if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) {
const auto count = d.vrequests_pending().v;
const auto &requesters = d.vrecent_requesters().v;
if (const auto chat = peer->asChat()) {
chat->setPendingRequestsCount(count, requesters);
} else if (const auto channel = peer->asChannel()) {
channel->setPendingRequestsCount(count, requesters);
}
}
} break;
case mtpc_updateServiceNotification: {
const auto &d = update.c_updateServiceNotification();
const auto text = TextWithEntities {
@@ -1976,7 +1990,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} else if (d.is_popup()) {
const auto &windows = session().windows();
if (!windows.empty()) {
windows.front()->window().show(Box<InformBox>(text));
windows.front()->window().show(Box<Ui::InformBox>(text));
}
} else {
session().data().serviceNotification(text, d.vmedia());
@@ -2100,6 +2114,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
auto &d = update.c_updateChannel();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
channel->inviter = UserId(0);
channel->inviteViaRequest = false;
if (channel->amIn()) {
if (channel->isMegagroup()
&& !channel->amCreator()

View File

@@ -0,0 +1,136 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_views.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_peer_id.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
namespace Api {
namespace {
// Send channel views each second.
constexpr auto kSendViewsTimeout = crl::time(1000);
} // namespace
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
: _session(&api->session())
, _api(&api->instance())
, _incrementTimer([=] { viewsIncrement(); }) {
}
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
auto peer = item->history()->peer;
auto i = _incremented.find(peer);
if (i != _incremented.cend()) {
if (i->second.contains(item->id)) {
return;
}
} else {
i = _incremented.emplace(peer).first;
}
i->second.emplace(item->id);
auto j = _toIncrement.find(peer);
if (j == _toIncrement.cend()) {
j = _toIncrement.emplace(peer).first;
_incrementTimer.callOnce(kSendViewsTimeout);
}
j->second.emplace(item->id);
}
void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
_incremented.remove(peer);
}
void ViewsManager::viewsIncrement() {
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
if (_incrementRequests.contains(i->first)) {
++i;
continue;
}
QVector<MTPint> ids;
ids.reserve(i->second.size());
for (const auto &msgId : i->second) {
ids.push_back(MTP_int(msgId));
}
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
i->first->input,
MTP_vector<MTPint>(ids),
MTP_bool(true)
)).done([=](
const MTPmessages_MessageViews &result,
mtpRequestId requestId) {
done(ids, result, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
fail(error, requestId);
}).afterDelay(5).send();
_incrementRequests.emplace(i->first, requestId);
i = _toIncrement.erase(i);
}
}
void ViewsManager::done(
QVector<MTPint> ids,
const MTPmessages_MessageViews &result,
mtpRequestId requestId) {
const auto &data = result.c_messages_messageViews();
auto &owner = _session->data();
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
auto &v = data.vviews().v;
if (ids.size() == v.size()) {
for (const auto &[peer, id] : _incrementRequests) {
if (id != requestId) {
continue;
}
const auto channel = peerToChannel(peer->id);
for (auto j = 0, l = int(ids.size()); j < l; ++j) {
if (const auto item = owner.message(channel, ids[j].v)) {
v[j].match([&](const MTPDmessageViews &data) {
if (const auto views = data.vviews()) {
item->setViewsCount(views->v);
}
if (const auto forwards = data.vforwards()) {
item->setForwardsCount(forwards->v);
}
if (const auto replies = data.vreplies()) {
item->setReplies(
HistoryMessageRepliesData(replies));
}
});
}
}
_incrementRequests.erase(peer);
break;
}
}
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
_incrementTimer.callOnce(kSendViewsTimeout);
}
}
void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) {
for (const auto &[peer, id] : _incrementRequests) {
if (id == requestId) {
_incrementRequests.erase(peer);
break;
}
}
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
_incrementTimer.callOnce(kSendViewsTimeout);
}
}
} // namespace Api

View File

@@ -0,0 +1,49 @@
/*
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
#include "mtproto/sender.h"
#include "base/timer.h"
class ApiWrap;
class PeerData;
namespace Main {
class Session;
} // namespace Main
namespace Api {
class ViewsManager final {
public:
explicit ViewsManager(not_null<ApiWrap*> api);
void scheduleIncrement(not_null<HistoryItem*> item);
void removeIncremented(not_null<PeerData*> peer);
private:
void viewsIncrement();
void done(
QVector<MTPint> ids,
const MTPmessages_MessageViews &result,
mtpRequestId requestId);
void fail(const MTP::Error &error, mtpRequestId requestId);
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _incremented;
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _toIncrement;
base::flat_map<not_null<PeerData*>, mtpRequestId> _incrementRequests;
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
base::Timer _incrementTimer;
};
} // namespace Api

View File

@@ -159,7 +159,7 @@ struct State {
auto &entry = context->cache(item);
entry.requestId = 0;
auto peers = std::vector<PeerId>();
peers.reserve(std::max(result.v.size(), 1));
peers.reserve(std::max(int(result.v.size()), 1));
for (const auto &id : result.v) {
peers.push_back(UserId(id));
}
@@ -355,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
} else if (UpdateUserpics(state, item, peers)) {
RegenerateParticipants(state, small, large);
pushNext();
} else if (peers.empty()) {
pushNext();
}
}, lifetime);

View File

@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_hash.h"
#include "api/api_invite_links.h"
#include "api/api_media.h"
#include "api/api_peer_photo.h"
#include "api/api_polls.h"
#include "api/api_sending.h"
#include "api/api_text_entities.h"
#include "api/api_self_destruct.h"
@@ -21,12 +23,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_global_privacy.h"
#include "api/api_updates.h"
#include "api/api_user_privacy.h"
#include "api/api_views.h"
#include "api/api_confirm_phone.h"
#include "data/stickers/data_stickers.h"
#include "data/data_drafts.h"
#include "data/data_changes.h"
#include "data/data_photo.h"
#include "data/data_web_page.h"
#include "data/data_poll.h"
#include "data/data_folder.h"
#include "data/data_media_types.h"
#include "data/data_sparse_ids.h"
@@ -60,7 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "main/main_account.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/stickers_box.h"
#include "boxes/sticker_set_box.h"
#include "window/notifications_manager.h"
@@ -71,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/item_text_options.h"
#include "ui/emoji_config.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/toasts/common_toasts.h"
#include "support/support_helper.h"
#include "storage/localimageloader.h"
#include "storage/download_manager_mtproto.h"
@@ -111,6 +115,7 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000);
constexpr auto kNotifySettingSaveTimeout = crl::time(1000);
constexpr auto kDialogsFirstLoad = 20;
constexpr auto kDialogsPerPage = 500;
constexpr auto kJoinErrorDuration = 5 * crl::time(1000);
using PhotoFileLocationId = Data::PhotoFileLocationId;
using DocumentFileLocationId = Data::DocumentFileLocationId;
@@ -141,15 +146,14 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
, _views(std::make_unique<Api::ViewsManager>(this))
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
, _polls(std::make_unique<Api::Polls>(this)) {
crl::on_main(session, [=] {
// You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet.
_session->uploader().photoReady(
) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
photoUploadReady(data.fullId, data.file);
}, _session->lifetime());
_session->data().chatsFilters().changed(
) | rpl::filter([=] {
return _session->data().chatsFilters().archiveNeeded();
@@ -352,7 +356,7 @@ void ApiWrap::checkChatInvite(
)).done(std::move(done)).fail(std::move(fail)).send();
}
void ApiWrap::importChatInvite(const QString &hash) {
void ApiWrap::importChatInvite(const QString &hash, bool isGroup) {
request(MTPmessages_ImportChatInvite(
MTP_string(hash)
)).done([=](const MTPUpdates &result) {
@@ -389,13 +393,20 @@ void ApiWrap::importChatInvite(const QString &hash) {
});
}).fail([=](const MTP::Error &error) {
const auto &type = error.type();
if (type == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(tr::lng_join_channel_error(tr::now)));
} else if (error.code() == 400) {
Ui::show(Box<InformBox>((type == qstr("USERS_TOO_MUCH"))
? tr::lng_group_invite_no_room(tr::now)
: tr::lng_group_invite_bad_link(tr::now)));
}
Ui::hideLayer();
Ui::ShowMultilineToast({ .text = { [&] {
if (type == qstr("INVITE_REQUEST_SENT")) {
return isGroup
? tr::lng_group_request_sent(tr::now)
: tr::lng_group_request_sent_channel(tr::now);
} else if (type == qstr("CHANNELS_TOO_MUCH")) {
return tr::lng_join_channel_error(tr::now);
} else if (type == qstr("USERS_TOO_MUCH")) {
return tr::lng_group_invite_no_room(tr::now);
} else {
return tr::lng_group_invite_bad_link(tr::now);
}
}() }, .duration = kJoinErrorDuration });
}).send();
}
@@ -464,19 +475,19 @@ void ApiWrap::sendMessageFail(
uint64 randomId,
FullMsgId itemId) {
if (error.type() == qstr("PEER_FLOOD")) {
Ui::show(Box<InformBox>(
Ui::show(Box<Ui::InformBox>(
PeerFloodErrorText(&session(), PeerFloodType::Send)));
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
const auto link = textcmdLink(
session().createInternalLinkFull(qsl("spambot")),
tr::lng_cant_more_info(tr::now));
Ui::show(Box<InformBox>(tr::lng_error_public_groups_denied(
Ui::show(Box<Ui::InformBox>(tr::lng_error_public_groups_denied(
tr::now,
lt_more_info,
link)));
} else if (error.type().startsWith(qstr("SLOWMODE_WAIT_"))) {
const auto chop = qstr("SLOWMODE_WAIT_").size();
const auto left = error.type().midRef(chop).toInt();
const auto left = base::StringViewMid(error.type(), chop).toInt();
if (const auto channel = peer->asChannel()) {
const auto seconds = channel->slowmodeSeconds();
if (seconds >= left) {
@@ -491,7 +502,7 @@ void ApiWrap::sendMessageFail(
Assert(peer->isUser());
if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) {
scheduled.removeSending(item);
Ui::show(Box<InformBox>(tr::lng_cant_do_this(tr::now)));
Ui::show(Box<Ui::InformBox>(tr::lng_cant_do_this(tr::now)));
}
}
if (const auto item = _session->data().message(itemId)) {
@@ -558,7 +569,8 @@ void ApiWrap::resolveMessageDatas() {
)).done([=](
const MTPmessages_Messages &result,
mtpRequestId requestId) {
gotMessageDatas(nullptr, result, requestId);
_session->data().processExistingMessages(nullptr, result);
finalizeMessageDataRequest(nullptr, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finalizeMessageDataRequest(nullptr, requestId);
}).afterDelay(kSmallDelayMs).send();
@@ -584,7 +596,8 @@ void ApiWrap::resolveMessageDatas() {
)).done([=](
const MTPmessages_Messages &result,
mtpRequestId requestId) {
gotMessageDatas(channel, result, requestId);
_session->data().processExistingMessages(channel, result);
finalizeMessageDataRequest(channel, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finalizeMessageDataRequest(channel, requestId);
}).afterDelay(kSmallDelayMs).send();
@@ -600,37 +613,6 @@ void ApiWrap::resolveMessageDatas() {
}
}
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
const auto handleResult = [&](auto &&result) {
_session->data().processUsers(result.vusers());
_session->data().processChats(result.vchats());
_session->data().processMessages(
result.vmessages(),
NewMessageType::Existing);
};
switch (msgs.type()) {
case mtpc_messages_messages:
handleResult(msgs.c_messages_messages());
break;
case mtpc_messages_messagesSlice:
handleResult(msgs.c_messages_messagesSlice());
break;
case mtpc_messages_channelMessages: {
auto &d = msgs.c_messages_channelMessages();
if (channel) {
channel->ptsReceived(d.vpts().v);
} else {
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
}
handleResult(d);
} break;
case mtpc_messages_messagesNotModified:
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
break;
}
finalizeMessageDataRequest(channel, requestId);
}
void ApiWrap::finalizeMessageDataRequest(
ChannelData *channel,
mtpRequestId requestId) {
@@ -662,8 +644,8 @@ QString ApiWrap::exportDirectMessageLink(
const auto fallback = [&] {
auto linkChannel = channel;
auto linkItemId = item->id;
auto linkCommentId = 0;
auto linkThreadId = 0;
auto linkCommentId = MsgId();
auto linkThreadId = MsgId();
if (inRepliesContext) {
if (const auto rootId = item->replyToTop()) {
const auto root = item->history()->owner().message(
@@ -693,11 +675,11 @@ QString ApiWrap::exportDirectMessageLink(
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
const auto query = base
+ '/'
+ QString::number(linkItemId)
+ QString::number(linkItemId.bare)
+ (linkCommentId
? "?comment=" + QString::number(linkCommentId)
? "?comment=" + QString::number(linkCommentId.bare)
: linkThreadId
? "?thread=" + QString::number(linkThreadId)
? "?thread=" + QString::number(linkThreadId.bare)
: "");
if (linkChannel->hasUsername()
&& !linkChannel->isMegagroup()
@@ -1123,7 +1105,9 @@ void ApiWrap::gotChatFull(
_fullPeerRequests.erase(i);
}
}
fullPeerUpdated().notify(peer);
_session->changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::FullInfo);
}
void ApiWrap::gotUserFull(
@@ -1146,7 +1130,9 @@ void ApiWrap::gotUserFull(
_fullPeerRequests.erase(i);
}
}
fullPeerUpdated().notify(user);
_session->changes().peerUpdated(
user,
Data::PeerUpdate::Flag::FullInfo);
}
void ApiWrap::requestPeer(not_null<PeerData*> peer) {
@@ -1278,7 +1264,7 @@ void ApiWrap::migrateDone(
void ApiWrap::migrateFail(not_null<PeerData*> peer, const MTP::Error &error) {
const auto &type = error.type();
if (type == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(tr::lng_migrate_error(tr::now)));
Ui::show(Box<Ui::InformBox>(tr::lng_migrate_error(tr::now)));
}
if (auto handlers = _migrateCallbacks.take(peer)) {
for (auto &handler : *handlers) {
@@ -1571,7 +1557,9 @@ void ApiWrap::applyLastParticipantsList(
(Data::PeerUpdate::Flag::Members | Data::PeerUpdate::Flag::Admins));
channel->mgInfo->botStatus = botStatus;
fullPeerUpdated().notify(channel);
_session->changes().peerUpdated(
channel,
Data::PeerUpdate::Flag::FullInfo);
}
void ApiWrap::applyBotsList(
@@ -1620,7 +1608,9 @@ void ApiWrap::applyBotsList(
}
channel->mgInfo->botStatus = botStatus;
fullPeerUpdated().notify(channel);
_session->changes().peerUpdated(
channel,
Data::PeerUpdate::Flag::FullInfo);
}
void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
@@ -1628,9 +1618,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
return;
}
const auto finalize = [=](UserId inviter, TimeId inviteDate) {
const auto finalize = [=](
UserId inviter = -1,
TimeId inviteDate = 0,
bool inviteViaRequest = false) {
channel->inviter = inviter;
channel->inviteDate = inviteDate;
channel->inviteViaRequest = inviteViaRequest;
if (const auto history = _session->data().historyLoaded(channel)) {
if (history->lastMessageKnown()) {
history->checkLocalMessages();
@@ -1651,7 +1645,10 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
const auto &participant = data.vparticipant();
participant.match([&](const MTPDchannelParticipantSelf &data) {
finalize(data.vinviter_id().v, data.vdate().v);
finalize(
data.vinviter_id().v,
data.vdate().v,
data.is_via_invite());
}, [&](const MTPDchannelParticipantCreator &) {
if (channel->mgInfo) {
channel->mgInfo->creator = _session->user();
@@ -1664,13 +1661,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
finalize(inviter, data.vdate().v);
}, [&](const MTPDchannelParticipantBanned &data) {
LOG(("API Error: Got self banned participant."));
finalize(-1, 0);
finalize();
}, [&](const MTPDchannelParticipant &data) {
LOG(("API Error: Got self regular participant."));
finalize(-1, 0);
finalize();
}, [&](const MTPDchannelParticipantLeft &data) {
LOG(("API Error: Got self left participant."));
finalize(-1, 0);
finalize();
});
});
}).fail([=](const MTP::Error &error) {
@@ -1678,7 +1675,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
if (error.type() == qstr("CHANNEL_PRIVATE")) {
channel->privateErrorReceived();
}
finalize(-1, 0);
finalize();
}).afterDelay(kSmallDelayMs).send();
}
@@ -1759,7 +1756,7 @@ void ApiWrap::deleteAllFromUser(
? history->collectMessagesFromUserToDelete(from)
: QVector<MsgId>();
const auto channelId = peerToChannel(channel->id);
for (const auto msgId : ids) {
for (const auto &msgId : ids) {
if (const auto item = _session->data().message(channelId, msgId)) {
item->destroy();
}
@@ -2105,19 +2102,35 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
_channelAmInRequests.remove(channel);
applyUpdates(result);
}).fail([=](const MTP::Error &error) {
if (error.type() == qstr("CHANNEL_PRIVATE")
const auto &type = error.type();
if (type == qstr("CHANNEL_PRIVATE")
&& channel->invitePeekExpires()) {
channel->privateErrorReceived();
} else if (error.type() == qstr("CHANNEL_PRIVATE")
|| error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA")
|| error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
Ui::show(Box<InformBox>(channel->isMegagroup()
? tr::lng_group_not_accessible(tr::now)
: tr::lng_channel_not_accessible(tr::now)));
} else if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(tr::lng_join_channel_error(tr::now)));
} else if (error.type() == qstr("USERS_TOO_MUCH")) {
Ui::show(Box<InformBox>(tr::lng_group_full(tr::now)));
} else {
const auto text = [&] {
if (type == qstr("INVITE_REQUEST_SENT")) {
return channel->isMegagroup()
? tr::lng_group_request_sent(tr::now)
: tr::lng_group_request_sent_channel(tr::now);
} else if (type == qstr("CHANNEL_PRIVATE")
|| type == qstr("CHANNEL_PUBLIC_GROUP_NA")
|| type == qstr("USER_BANNED_IN_CHANNEL")) {
return channel->isMegagroup()
? tr::lng_group_not_accessible(tr::now)
: tr::lng_channel_not_accessible(tr::now);
} else if (type == qstr("CHANNELS_TOO_MUCH")) {
return tr::lng_join_channel_error(tr::now);
} else if (type == qstr("USERS_TOO_MUCH")) {
return tr::lng_group_full(tr::now);
}
return QString();
}();
if (!text.isEmpty()) {
Ui::ShowMultilineToast({
.text = { text },
.duration = kJoinErrorDuration,
});
}
}
_channelAmInRequests.remove(channel);
}).send();
@@ -2220,7 +2233,7 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
void ApiWrap::updatePrivacyLastSeens() {
const auto now = base::unixtime::now();
_session->data().enumerateUsers([&](UserData *user) {
if (user->isSelf() || !user->isFullLoaded()) {
if (user->isSelf() || !user->isLoaded()) {
return;
}
if (user->onlineTill <= 0) {
@@ -3289,7 +3302,7 @@ void ApiWrap::requestMessageAfterDate(
// So we request a message with offset_date = desired_date - 1 and add_offset = -1.
// This should give us the first message with date >= desired_date.
const auto offsetId = 0;
const auto offsetDate = static_cast<int>(base::QDateToDateTime(date).toTime_t()) - 1;
const auto offsetDate = static_cast<int>(date.startOfDay().toSecsSinceEpoch()) - 1;
const auto addOffset = -1;
const auto limit = 1;
const auto maxId = 0;
@@ -3837,7 +3850,7 @@ void ApiWrap::sendSharedContact(
MTP_string(lastName),
MTP_string(), // vcard
MTP_long(userId.bare)),
MTPReplyMarkup());
HistoryMessageMarkupData());
const auto media = MTP_inputMediaContact(
MTP_string(phone),
@@ -4101,7 +4114,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
messagePostAuthor,
sending,
media,
MTPReplyMarkup());
HistoryMessageMarkupData());
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_SendMessage(
MTP_flags(sendFlags),
@@ -4525,105 +4538,6 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
action.replaceMediaOf);
}
void ApiWrap::uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image) {
peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto(
instance().mainDcId(),
peer->id,
std::move(image));
const auto fakeId = FullMsgId(
peerToChannel(peer->id),
_session->data().nextLocalMessageId());
const auto already = ranges::find(
_peerPhotoUploads,
peer,
[](const auto &pair) { return pair.second; });
if (already != end(_peerPhotoUploads)) {
_session->uploader().cancel(already->first);
_peerPhotoUploads.erase(already);
}
_peerPhotoUploads.emplace(fakeId, peer);
_session->uploader().uploadMedia(fakeId, ready);
}
void ApiWrap::photoUploadReady(
const FullMsgId &msgId,
const MTPInputFile &file) {
if (const auto maybePeer = _peerPhotoUploads.take(msgId)) {
const auto peer = *maybePeer;
const auto applier = [=](const MTPUpdates &result) {
applyUpdates(result);
};
if (peer->isSelf()) {
request(MTPphotos_UploadProfilePhoto(
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble() // video_start_ts
)).done([=](const MTPphotos_Photo &result) {
result.match([&](const MTPDphotos_photo &data) {
_session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers());
});
}).send();
} else if (const auto chat = peer->asChat()) {
const auto history = _session->data().history(chat);
history->sendRequestId = request(MTPmessages_EditChatPhoto(
chat->inputChat,
MTP_inputChatUploadedPhoto(
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble()) // video_start_ts
)).done(applier).afterRequest(history->sendRequestId).send();
} else if (const auto channel = peer->asChannel()) {
const auto history = _session->data().history(channel);
history->sendRequestId = request(MTPchannels_EditPhoto(
channel->inputChannel,
MTP_inputChatUploadedPhoto(
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
file,
MTPInputFile(), // video
MTPdouble()) // video_start_ts
)).done(applier).afterRequest(history->sendRequestId).send();
}
}
}
void ApiWrap::clearPeerPhoto(not_null<PhotoData*> photo) {
const auto self = _session->user();
if (self->userpicPhotoId() == photo->id) {
request(MTPphotos_UpdateProfilePhoto(
MTP_inputPhotoEmpty()
)).done([=](const MTPphotos_Photo &result) {
self->setPhoto(MTP_userProfilePhotoEmpty());
}).send();
} else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) {
const auto applier = [=](const MTPUpdates &result) {
applyUpdates(result);
};
if (const auto chat = photo->peer->asChat()) {
request(MTPmessages_EditChatPhoto(
chat->inputChat,
MTP_inputChatPhotoEmpty()
)).done(applier).send();
} else if (const auto channel = photo->peer->asChannel()) {
request(MTPchannels_EditPhoto(
channel->inputChannel,
MTP_inputChatPhotoEmpty()
)).done(applier).send();
}
} else {
request(MTPphotos_DeletePhotos(
MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
)).send();
_session->storage().remove(Storage::UserPhotosRemoveOne(
peerToUser(self->id),
photo->id));
}
}
void ApiWrap::reloadContactSignupSilent() {
if (_contactSignupSilentRequestId) {
return;
@@ -4666,34 +4580,27 @@ void ApiWrap::saveContactSignupSilent(bool silent) {
_contactSignupSilentRequestId = requestId;
}
void ApiWrap::saveSelfBio(const QString &text, FnMut<void()> done) {
if (_saveBioRequestId) {
if (text != _saveBioText) {
request(_saveBioRequestId).cancel();
void ApiWrap::saveSelfBio(const QString &text) {
if (_bio.requestId) {
if (text != _bio.requestedText) {
request(_bio.requestId).cancel();
} else {
if (done) {
_saveBioDone = std::move(done);
}
return;
}
}
_saveBioText = text;
_saveBioDone = std::move(done);
_saveBioRequestId = request(MTPaccount_UpdateProfile(
_bio.requestedText = text;
_bio.requestId = request(MTPaccount_UpdateProfile(
MTP_flags(MTPaccount_UpdateProfile::Flag::f_about),
MTPstring(),
MTPstring(),
MTP_string(text)
)).done([=](const MTPUser &result) {
_saveBioRequestId = 0;
_bio.requestId = 0;
_session->data().processUsers(MTP_vector<MTPUser>(1, result));
_session->user()->setAbout(_saveBioText);
if (_saveBioDone) {
_saveBioDone();
}
_session->data().processUser(result);
_session->user()->setAbout(_bio.requestedText);
}).fail([=](const MTP::Error &error) {
_saveBioRequestId = 0;
_bio.requestId = 0;
}).send();
}
@@ -4733,167 +4640,18 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
return *_inviteLinks;
}
void ApiWrap::createPoll(
const PollData &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail) {
sendAction(action);
const auto history = action.history;
const auto peer = history->peer;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
const auto clearCloudDraft = action.clearDraft;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearLocalDraft();
history->clearCloudDraft();
history->startSavingCloudDraft();
}
const auto silentPost = ShouldSendSilent(peer, action.options);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto replyTo = action.replyTo;
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(base::RandomValue<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled)
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) mutable {
applyUpdates(result);
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
_session->changes().historyUpdated(
history,
(action.options.scheduled
? Data::HistoryUpdate::Flag::ScheduledSent
: Data::HistoryUpdate::Flag::MessageSent));
done();
finish();
}).fail([=](
const MTP::Error &error,
const MTP::Response &response) mutable {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
UnixtimeFromMsgId(response.outerMsgId));
}
fail(error);
finish();
}).afterRequest(history->sendRequestId
).send();
return history->sendRequestId;
});
Api::ViewsManager &ApiWrap::views() {
return *_views;
}
void ApiWrap::sendPollVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options) {
if (_pollVotesRequestIds.contains(itemId)) {
return;
}
const auto item = _session->data().message(itemId);
const auto media = item ? item->media() : nullptr;
const auto poll = media ? media->poll() : nullptr;
if (!item) {
return;
}
const auto showSending = poll && !options.empty();
const auto hideSending = [=] {
if (showSending) {
if (const auto item = _session->data().message(itemId)) {
poll->sendingVotes.clear();
_session->data().requestItemRepaint(item);
}
}
};
if (showSending) {
poll->sendingVotes = options;
_session->data().requestItemRepaint(item);
}
auto prepared = QVector<MTPbytes>();
prepared.reserve(options.size());
ranges::transform(
options,
ranges::back_inserter(prepared),
[](const QByteArray &option) { return MTP_bytes(option); });
const auto requestId = request(MTPmessages_SendVote(
item->history()->peer->input,
MTP_int(item->id),
MTP_vector<MTPbytes>(prepared)
)).done([=](const MTPUpdates &result) {
_pollVotesRequestIds.erase(itemId);
hideSending();
applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollVotesRequestIds.erase(itemId);
hideSending();
}).send();
_pollVotesRequestIds.emplace(itemId, requestId);
Api::ConfirmPhone &ApiWrap::confirmPhone() {
return *_confirmPhone;
}
void ApiWrap::closePoll(not_null<HistoryItem*> item) {
const auto itemId = item->fullId();
if (_pollCloseRequestIds.contains(itemId)) {
return;
}
const auto media = item ? item->media() : nullptr;
const auto poll = media ? media->poll() : nullptr;
if (!poll) {
return;
}
const auto requestId = request(MTPmessages_EditMessage(
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
item->history()->peer->input,
MTP_int(item->id),
MTPstring(),
PollDataToInputMedia(poll, true),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date
)).done([=](const MTPUpdates &result) {
_pollCloseRequestIds.erase(itemId);
applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollCloseRequestIds.erase(itemId);
}).send();
_pollCloseRequestIds.emplace(itemId, requestId);
Api::PeerPhoto &ApiWrap::peerPhoto() {
return *_peerPhoto;
}
void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
const auto itemId = item->fullId();
if (!IsServerMsgId(item->id)
|| _pollReloadRequestIds.contains(itemId)) {
return;
}
const auto requestId = request(MTPmessages_GetPollResults(
item->history()->peer->input,
MTP_int(item->id)
)).done([=](const MTPUpdates &result) {
_pollReloadRequestIds.erase(itemId);
applyUpdates(result);
}).fail([=](const MTP::Error &error) {
_pollReloadRequestIds.erase(itemId);
}).send();
_pollReloadRequestIds.emplace(itemId, requestId);
Api::Polls &ApiWrap::polls() {
return *_polls;
}

View File

@@ -62,6 +62,10 @@ class SensitiveContent;
class GlobalPrivacy;
class UserPrivacy;
class InviteLinks;
class ViewsManager;
class ConfirmPhone;
class PeerPhoto;
class Polls;
namespace details {
@@ -198,7 +202,7 @@ public:
const QString &hash,
FnMut<void(const MTPChatInvite &)> done,
Fn<void(const MTP::Error &)> fail);
void importChatInvite(const QString &hash);
void importChatInvite(const QString &hash, bool isGroup);
void requestChannelMembersForAdd(
not_null<ChannelData*> channel,
@@ -266,10 +270,6 @@ public:
void clearHistory(not_null<PeerData*> peer, bool revoke);
void deleteConversation(not_null<PeerData*> peer, bool revoke);
base::Observable<PeerData*> &fullPeerUpdated() {
return _fullPeerUpdated;
}
bool isQuitPrevent();
void jumpToDate(Dialogs::Key chat, const QDate &date);
@@ -381,15 +381,12 @@ public:
uint64 randomId = 0,
FullMsgId itemId = FullMsgId());
void uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image);
void clearPeerPhoto(not_null<PhotoData*> photo);
void reloadContactSignupSilent();
rpl::producer<bool> contactSignupSilent() const;
std::optional<bool> contactSignupSilentCurrent() const;
void saveContactSignupSilent(bool silent);
void saveSelfBio(const QString &text, FnMut<void()> done);
void saveSelfBio(const QString &text);
[[nodiscard]] Api::Authorizations &authorizations();
[[nodiscard]] Api::AttachedStickers &attachedStickers();
@@ -400,17 +397,10 @@ public:
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
[[nodiscard]] Api::UserPrivacy &userPrivacy();
[[nodiscard]] Api::InviteLinks &inviteLinks();
void createPoll(
const PollData &data,
const SendAction &action,
Fn<void()> done,
Fn<void(const MTP::Error &error)> fail);
void sendPollVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options);
void closePoll(not_null<HistoryItem*> item);
void reloadPollResults(not_null<HistoryItem*> item);
[[nodiscard]] Api::ViewsManager &views();
[[nodiscard]] Api::ConfirmPhone &confirmPhone();
[[nodiscard]] Api::PeerPhoto &peerPhoto();
[[nodiscard]] Api::Polls &polls();
void updatePrivacyLastSeens();
@@ -456,7 +446,6 @@ private:
void saveDraftsToCloud();
void resolveMessageDatas();
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
void finalizeMessageDataRequest(
ChannelData *channel,
mtpRequestId requestId);
@@ -573,8 +562,6 @@ private:
FileReferencesHandler &&handler,
Request &&data);
void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file);
void migrateDone(
not_null<PeerData*> peer,
not_null<ChannelData*> channel);
@@ -672,8 +659,6 @@ private:
std::unique_ptr<TaskQueue> _fileLoader;
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
base::Observable<PeerData*> _fullPeerUpdated;
mtpRequestId _topPromotionRequestId = 0;
std::pair<QString, uint32> _topPromotionKey;
TimeId _topPromotionNextRequestTime = TimeId(0);
@@ -705,11 +690,10 @@ private:
std::vector<FnMut<void(const MTPUser &)>> _supportContactCallbacks;
base::flat_map<FullMsgId, not_null<PeerData*>> _peerPhotoUploads;
mtpRequestId _saveBioRequestId = 0;
FnMut<void()> _saveBioDone;
QString _saveBioText;
struct {
mtpRequestId requestId = 0;
QString requestedText;
} _bio;
const std::unique_ptr<Api::Authorizations> _authorizations;
const std::unique_ptr<Api::AttachedStickers> _attachedStickers;
@@ -720,10 +704,10 @@ private:
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
const std::unique_ptr<Api::ViewsManager> _views;
const std::unique_ptr<Api::ConfirmPhone> _confirmPhone;
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
const std::unique_ptr<Api::Polls> _polls;
mtpRequestId _wallPaperRequestId = 0;
QString _wallPaperSlug;

View File

@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/text/text_utilities.h"
@@ -104,7 +104,8 @@ void AboutBox::showVersionHistory() {
QGuiApplication::clipboard()->setText(url);
Ui::show(Box<InformBox>("The link to the current private alpha version of Telegram Desktop was copied to the clipboard."));
Ui::show(Box<Ui::InformBox>("The link to the current private alpha "
"version of Telegram Desktop was copied to the clipboard."));
} else {
UrlClickHandler::Open(Core::App().changelogLink());
}

View File

@@ -0,0 +1,68 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/about_sponsored_box.h"
#include "lang/lang_keys.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include <QtGui/QDesktopServices>
namespace Ui {
namespace {
constexpr auto kUrl = "https://promote.telegram.org"_cs;
} // namespace
void AboutSponsoredBox(not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_sponsored_title());
box->setWidth(st::boxWideWidth);
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
const auto addUrl = [&] {
const auto &st = st::sponsoredUrlButton;
const auto row = box->addRow(object_ptr<RpWidget>(box));
row->resize(0, st.height + st.padding.top() + st.padding.bottom());
const auto button = Ui::CreateChild<RoundButton>(
row,
rpl::single<QString>(kUrl.utf8()),
st);
button->setBrushOverride(Qt::NoBrush);
button->setPenOverride(QPen(st::historyLinkInFg));
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
rpl::combine(
row->sizeValue(),
button->sizeValue()
) | rpl::start_with_next([=](
const QSize &rowSize,
const QSize &buttonSize) {
button->moveToLeft(
(rowSize.width() - buttonSize.width()) / 2,
(rowSize.height() - buttonSize.height()) / 2);
}, row->lifetime());
button->addClickHandler([=] {
QDesktopServices::openUrl({ kUrl.utf8() });
});
};
const auto &stLabel = st::aboutLabel;
const auto info1 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
info1->setText(tr::lng_sponsored_info_description1(tr::now));
box->addSkip(st::sponsoredUrlButtonSkip);
addUrl();
box->addSkip(st::sponsoredUrlButtonSkip);
const auto info2 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
info2->setText(tr::lng_sponsored_info_description2(tr::now));
}
} // namespace Ui

View File

@@ -0,0 +1,16 @@
/*
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
#include "ui/layers/generic_box.h"
namespace Ui {
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
} // namespace Ui

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "mtproto/sender.h"
#include "styles/style_widgets.h"
#include <QtCore/QTimer>
class ConfirmBox;
class PeerListBox;
namespace Window {
@@ -25,6 +22,7 @@ class Session;
} // namespace Main
namespace Ui {
class ConfirmBox;
class FlatLabel;
class InputField;
class PhoneInput;
@@ -118,7 +116,10 @@ protected:
private:
void createChannel(const QString &title, const QString &description);
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
void createGroup(
not_null<PeerListBox*> selectUsersBox,
const QString &title,
const std::vector<not_null<PeerData*>> &users);
void submitName();
void submit();
void checkInviteLink();
@@ -176,12 +177,10 @@ private:
void check();
void save();
void updateDone(const MTPBool &result);
void updateFail(const MTP::Error &error);
void updateFail(const QString &error);
void checkDone(const MTPBool &result);
void checkFail(const MTP::Error &error);
void firstCheckFail(const MTP::Error &error);
void checkFail(const QString &error);
void firstCheckFail(const QString &error);
void updateMaxHeight();
@@ -192,6 +191,7 @@ private:
MTP::Sender _api;
bool _existing = false;
bool _creatingInviteLink = false;
std::shared_ptr<Ui::RadioenumGroup<Privacy>> _privacyGroup;
object_ptr<Ui::Radioenum<Privacy>> _public;
@@ -209,7 +209,7 @@ private:
mtpRequestId _checkRequestId = 0;
QString _sentUsername, _checkUsername, _errorText, _goodText;
QTimer _checkTimer;
base::Timer _checkTimer;
};
@@ -226,8 +226,7 @@ protected:
private:
void submit();
void save();
void saveSelfDone(const MTPUser &user);
void saveSelfFail(const MTP::Error &error);
void saveSelfFail(const QString &error);
const not_null<UserData*> _user;
MTP::Sender _api;

View File

@@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "boxes/background_preview_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "window/window_session_controller.h"
#include "window/themes/window_theme.h"
#include "styles/style_overview.h"
@@ -176,7 +176,7 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) {
)).send();
};
_controller->show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_background_sure_delete(tr::now),
tr::lng_selected_delete(tr::now),
tr::lng_cancel(tr::now),

View File

@@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_resolver.h"
#include "data/data_file_origin.h"
#include "base/unixtime.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/background_preview_box.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
const auto frames = framesForStyle(st);
auto &image = frames->image;
const auto count = int(frames->ready.size());
const auto index = int(std::round(toggled * (count - 1)));
const auto index = int(base::SafeRound(toggled * (count - 1)));
Assert(index >= 0 && index < count);
if (!frames->ready[index]) {
frames->ready[index] = true;
@@ -288,7 +288,6 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
bool out) {
Expects(history->peer->isUser());
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
const auto flags = MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| (out ? MessageFlag::Outgoing : MessageFlag(0));
@@ -296,7 +295,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
const auto viaBotId = UserId();
const auto groupedId = uint64();
const auto item = history->makeMessage(
++id,
history->nextNonHistoryEntryId(),
flags,
replyTo,
viaBotId,
@@ -305,7 +304,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
QString(),
TextWithEntities{ TextUtilities::Clean(text) },
MTP_messageMediaEmpty(),
MTPReplyMarkup(),
HistoryMessageMarkupData(),
groupedId);
return AdminLog::OwnedItem(delegate, item);
}
@@ -785,7 +784,7 @@ bool BackgroundPreviewBox::Start(
}
if (!IsValidWallPaperSlug(slug)) {
controller->show(
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
Box<Ui::InformBox>(tr::lng_background_bad_link(tr::now)));
return false;
}
controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
@@ -795,7 +794,7 @@ bool BackgroundPreviewBox::Start(
result.withUrlParams(params)));
}), crl::guard(controller, [=](const MTP::Error &error) {
controller->show(
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
Box<Ui::InformBox>(tr::lng_background_bad_link(tr::now)));
}));
return true;
}

View File

@@ -82,29 +82,40 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
maxHeight: 24px;
textFg: windowBoldFg;
style: TextStyle(defaultTextStyle) {
font: font(16px semibold);
linkFont: font(16px semibold);
linkFontOver: font(16px semibold underline);
font: font(18px semibold);
linkFont: font(18px semibold);
linkFontOver: font(18px semibold underline);
}
}
confirmInviteStatus: FlatLabel(boxLabel) {
confirmInviteAbout: FlatLabel(boxLabel) {
align: align(center);
minWidth: 320px;
textFg: windowSubTextFg;
maxHeight: 60px;
style: TextStyle(boxLabelStyle) {
lineHeight: 19px;
}
}
confirmInviteTitleTop: 106px;
confirmInvitePhotoSize: 76px;
confirmInvitePhotoTop: 20px;
confirmInviteStatusTop: 136px;
confirmInviteUserHeight: 84px;
confirmInviteUserPhotoSize: 56px;
confirmInviteUserPhotoTop: 166px;
confirmInviteStatus: FlatLabel(confirmInviteAbout) {
textFg: windowSubTextFg;
style: boxLabelStyle;
maxHeight: 0px;
}
confirmInviteAboutPadding: margins(36px, 4px, 36px, 10px);
confirmInviteAboutRequestsPadding: margins(36px, 9px, 36px, 15px);
confirmInviteTitleTop: 141px;
confirmInvitePhotoSize: 96px;
confirmInvitePhotoTop: 33px;
confirmInviteStatusTop: 164px;
confirmInviteUserHeight: 100px;
confirmInviteUserPhotoSize: 50px;
confirmInviteUserPhotoTop: 210px;
confirmInviteUsersWidth: 320px;
confirmInviteUserName: FlatLabel(defaultFlatLabel) {
align: align(center);
minWidth: 66px;
maxHeight: 20px;
}
confirmInviteUserNameTop: 227px;
confirmInviteUserNameTop: 264px;
confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) {
minWidth: 272px;
@@ -363,7 +374,7 @@ aboutVersionLink: LinkButton(defaultLinkButton) {
aboutTextTop: 34px;
aboutSkip: 14px;
aboutLabel: FlatLabel(defaultFlatLabel) {
minWidth: 330px;
minWidth: 300px;
align: align(topleft);
style: TextStyle(defaultTextStyle) {
lineHeight: 22px;
@@ -973,3 +984,48 @@ autolockTimeField: InputField(scheduleTimeField) {
heightMin: 20px;
}
autolockTimeWidth: 52px;
sponsoredUrlButtonSkip: 11px;
sponsoredUrlButton: RoundButton(defaultActiveButton) {
height: 32px;
width: -42px;
textBg: transparent;
textBgOver: transparent;
radius: roundRadiusLarge;
padding: margins(2px, 2px, 2px, 2px);
textFg: historyLinkInFg;
textFgOver: historyLinkInFg;
textTop: 7px;
font: normalFont;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
requestsBoxItem: PeerListItem(peerListBoxItem) {
height: 99px;
button: OutlineButton(defaultPeerListButton) {
textBg: contactsBg;
textBgOver: contactsBg;
ripple: RippleAnimation(defaultRippleAnimation) {
color: contactsBgOver;
}
}
}
requestsBoxList: PeerList(peerListBox) {
padding: margins(0px, 12px, 0px, 12px);
item: requestsBoxItem;
}
requestsAcceptButton: RoundButton(defaultActiveButton) {
width: -28px;
height: 30px;
textTop: 6px;
}
requestsRejectButton: RoundButton(defaultLightButton) {
width: -28px;
height: 30px;
textTop: 6px;
}
requestAcceptPosition: point(71px, 58px);
requestButtonsSkip: 9px;

View File

@@ -9,26 +9,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/sent_code_field.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "ui/special_fields.h"
#include "boxes/confirm_phone_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/phone_banned_box.h"
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "mtproto/sender.h"
#include "apiwrap.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
namespace {
void createErrorLabel(
void CreateErrorLabel(
QWidget *parent,
object_ptr<Ui::FadeWrap<Ui::FlatLabel>> &label,
const QString &text,
@@ -67,7 +68,7 @@ void createErrorLabel(
class ChangePhoneBox::EnterPhone : public Ui::BoxContent {
public:
EnterPhone(QWidget*, not_null<Main::Session*> session);
EnterPhone(QWidget*, not_null<Window::SessionController*> controller);
void setInnerFocus() override {
_phone->setFocusFast();
@@ -78,14 +79,16 @@ protected:
private:
void submit();
void sendPhoneDone(const MTPauth_SentCode &result, const QString &phoneNumber);
void sendPhoneDone(
const MTPauth_SentCode &result,
const QString &phoneNumber);
void sendPhoneFail(const MTP::Error &error, const QString &phoneNumber);
void showError(const QString &text);
void hideError() {
showError(QString());
}
const not_null<Main::Session*> _session;
const not_null<Window::SessionController*> _controller;
MTP::Sender _api;
object_ptr<Ui::PhoneInput> _phone = { nullptr };
@@ -129,41 +132,51 @@ private:
QString _hash;
int _codeLength = 0;
int _callTimeout = 0;
object_ptr<SentCodeField> _code = { nullptr };
object_ptr<Ui::SentCodeField> _code = { nullptr };
object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr };
object_ptr<Ui::FlatLabel> _callLabel = { nullptr };
mtpRequestId _requestId = 0;
SentCodeCall _call;
Ui::SentCodeCall _call;
};
ChangePhoneBox::EnterPhone::EnterPhone(
QWidget*,
not_null<Main::Session*> session)
: _session(session)
, _api(&session->mtp()) {
not_null<Window::SessionController*> controller)
: _controller(controller)
, _api(&controller->session().mtp()) {
}
void ChangePhoneBox::EnterPhone::prepare() {
setTitle(tr::lng_change_phone_title());
auto phoneValue = QString();
const auto phoneValue = QString();
_phone.create(
this,
st::defaultInputField,
tr::lng_change_phone_new_title(),
Countries::ExtractPhoneCode(_session->user()->phone()),
Countries::ExtractPhoneCode(_controller->session().user()->phone()),
phoneValue);
_phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height());
_phone->resize(
st::boxWidth - 2 * st::boxPadding.left(),
_phone->height());
_phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip);
connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
auto description = object_ptr<Ui::FlatLabel>(this, tr::lng_change_phone_new_description(tr::now), st::changePhoneLabel);
auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height;
description->moveToLeft(st::boxPadding.left(), _phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
const auto description = object_ptr<Ui::FlatLabel>(
this,
tr::lng_change_phone_new_description(tr::now),
st::changePhoneLabel);
const auto errorSkip = st::boxLittleSkip
+ st::changePhoneError.style.font->height;
description->moveToLeft(
st::boxPadding.left(),
_phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
setDimensions(st::boxWidth, description->bottomNoMargins() + st::boxLittleSkip);
setDimensions(
st::boxWidth,
description->bottomNoMargins() + st::boxLittleSkip);
addButton(tr::lng_change_phone_new_submit(), [this] { submit(); });
addButton(tr::lng_cancel(), [this] { closeBox(); });
@@ -175,13 +188,15 @@ void ChangePhoneBox::EnterPhone::submit() {
}
hideError();
auto phoneNumber = _phone->getLastText().trimmed();
const auto phoneNumber = _phone->getLastText().trimmed();
_requestId = _api.request(MTPaccount_SendChangePhoneCode(
MTP_string(phoneNumber),
MTP_codeSettings(MTP_flags(0))
)).done([=](const MTPauth_SentCode &result) {
_requestId = 0;
sendPhoneDone(result, phoneNumber);
}).fail([=](const MTP::Error &error) {
_requestId = 0;
sendPhoneFail(error, phoneNumber);
}).handleFloodErrors().send();
}
@@ -189,33 +204,45 @@ void ChangePhoneBox::EnterPhone::submit() {
void ChangePhoneBox::EnterPhone::sendPhoneDone(
const MTPauth_SentCode &result,
const QString &phoneNumber) {
Expects(result.type() == mtpc_auth_sentCode);
_requestId = 0;
using CodeData = const MTPDauth_sentCode&;
const auto &data = result.match([](const auto &data) -> CodeData {
return data;
});
auto codeLength = 0;
auto &data = result.c_auth_sentCode();
switch (data.vtype().type()) {
case mtpc_auth_sentCodeTypeApp:
const auto hasLength = data.vtype().match([&](
const MTPDauth_sentCodeTypeApp &typeData) {
LOG(("Error: should not be in-app code!"));
showError(Lang::Hard::ServerError());
return;
case mtpc_auth_sentCodeTypeSms: codeLength = data.vtype().c_auth_sentCodeTypeSms().vlength().v; break;
case mtpc_auth_sentCodeTypeCall: codeLength = data.vtype().c_auth_sentCodeTypeCall().vlength().v; break;
case mtpc_auth_sentCodeTypeFlashCall:
return false;
}, [&](const MTPDauth_sentCodeTypeSms &typeData) {
codeLength = typeData.vlength().v;
return true;
}, [&](const MTPDauth_sentCodeTypeCall &typeData) {
codeLength = typeData.vlength().v;
return true;
}, [&](const MTPDauth_sentCodeTypeFlashCall &typeData) {
LOG(("Error: should not be flashcall!"));
showError(Lang::Hard::ServerError());
return false;
});
if (!hasLength) {
return;
}
auto phoneCodeHash = qs(data.vphone_code_hash());
auto callTimeout = 0;
if (const auto nextType = data.vnext_type()) {
if (nextType->type() == mtpc_auth_codeTypeCall) {
callTimeout = data.vtimeout().value_or(60);
const auto phoneCodeHash = qs(data.vphone_code_hash());
const auto callTimeout = [&] {
if (const auto nextType = data.vnext_type()) {
return nextType->match([&](const MTPDauth_sentCodeTypeCall &) {
return data.vtimeout().value_or(60);
}, [](const auto &) {
return 0;
});
}
}
Ui::show(
return 0;
}();
_controller->show(
Box<EnterCode>(
_session,
&_controller->session(),
phoneNumber,
phoneCodeHash,
codeLength,
@@ -223,28 +250,36 @@ void ChangePhoneBox::EnterPhone::sendPhoneDone(
Ui::LayerOption::KeepOther);
}
void ChangePhoneBox::EnterPhone::sendPhoneFail(const MTP::Error &error, const QString &phoneNumber) {
_requestId = 0;
void ChangePhoneBox::EnterPhone::sendPhoneFail(
const MTP::Error &error,
const QString &phoneNumber) {
if (MTP::IsFloodError(error)) {
showError(tr::lng_flood_error(tr::now));
} else if (error.type() == qstr("PHONE_NUMBER_INVALID")) {
showError(tr::lng_bad_phone(tr::now));
} else if (error.type() == qstr("PHONE_NUMBER_BANNED")) {
ShowPhoneBannedError(phoneNumber);
Ui::ShowPhoneBannedError(&_controller->window(), phoneNumber);
} else if (error.type() == qstr("PHONE_NUMBER_OCCUPIED")) {
Ui::show(Box<InformBox>(
tr::lng_change_phone_occupied(
tr::now,
lt_phone,
Ui::FormatPhone(phoneNumber)),
tr::lng_box_ok(tr::now)));
_controller->show(
Box<Ui::InformBox>(
tr::lng_change_phone_occupied(
tr::now,
lt_phone,
Ui::FormatPhone(phoneNumber)),
tr::lng_box_ok(tr::now)),
Ui::LayerOption::CloseOther);
} else {
showError(Lang::Hard::ServerError());
}
}
void ChangePhoneBox::EnterPhone::showError(const QString &text) {
createErrorLabel(this, _error, text, st::boxPadding.left(), _phone->y() + _phone->height() + st::boxLittleSkip);
CreateErrorLabel(
this,
_error,
text,
st::boxPadding.left(),
_phone->y() + _phone->height() + st::boxLittleSkip);
if (!text.isEmpty()) {
_phone->showError();
}
@@ -269,16 +304,23 @@ ChangePhoneBox::EnterCode::EnterCode(
void ChangePhoneBox::EnterCode::prepare() {
setTitle(tr::lng_change_phone_title());
auto descriptionText = tr::lng_change_phone_code_description(
const auto descriptionText = tr::lng_change_phone_code_description(
tr::now,
lt_phone,
Ui::Text::Bold(Ui::FormatPhone(_phone)),
Ui::Text::WithEntities);
auto description = object_ptr<Ui::FlatLabel>(this, rpl::single(descriptionText), st::changePhoneLabel);
const auto description = object_ptr<Ui::FlatLabel>(
this,
rpl::single(descriptionText),
st::changePhoneLabel);
description->moveToLeft(st::boxPadding.left(), 0);
auto phoneValue = QString();
_code.create(this, st::defaultInputField, tr::lng_change_phone_code_title(), phoneValue);
const auto phoneValue = QString();
_code.create(
this,
st::defaultInputField,
tr::lng_change_phone_code_title(),
phoneValue);
_code->setAutoSubmit(_codeLength, [=] { submit(); });
_code->setChangedCallback([=] { hideError(); });
@@ -289,7 +331,7 @@ void ChangePhoneBox::EnterCode::prepare() {
setDimensions(st::boxWidth, countHeight());
if (_callTimeout > 0) {
_call.setStatus({ SentCodeCall::State::Waiting, _callTimeout });
_call.setStatus({ Ui::SentCodeCall::State::Waiting, _callTimeout });
updateCall();
}
@@ -298,7 +340,8 @@ void ChangePhoneBox::EnterCode::prepare() {
}
int ChangePhoneBox::EnterCode::countHeight() {
auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height;
const auto errorSkip = st::boxLittleSkip
+ st::changePhoneError.style.font->height;
return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip;
}
@@ -316,12 +359,14 @@ void ChangePhoneBox::EnterCode::submit() {
MTP_string(_hash),
MTP_string(code)
)).done([=](const MTPUser &result) {
_requestId = 0;
session->data().processUser(result);
if (weak) {
Ui::hideLayer();
}
Ui::Toast::Show(tr::lng_change_phone_success(tr::now));
}).fail(crl::guard(this, [=](const MTP::Error &error) {
_requestId = 0;
sendCodeFail(error);
})).handleFloodErrors().send();
}
@@ -336,12 +381,14 @@ void ChangePhoneBox::EnterCode::sendCall() {
}
void ChangePhoneBox::EnterCode::updateCall() {
auto text = _call.getText();
const auto text = _call.getText();
if (text.isEmpty()) {
_callLabel.destroy();
} else if (!_callLabel) {
_callLabel.create(this, text, st::changePhoneLabel);
_callLabel->moveToLeft(st::boxPadding.left(), countHeight() - _callLabel->height());
_callLabel->moveToLeft(
st::boxPadding.left(),
countHeight() - _callLabel->height());
_callLabel->show();
} else {
_callLabel->setText(text);
@@ -349,17 +396,22 @@ void ChangePhoneBox::EnterCode::updateCall() {
}
void ChangePhoneBox::EnterCode::showError(const QString &text) {
createErrorLabel(this, _error, text, st::boxPadding.left(), _code->y() + _code->height() + st::boxLittleSkip);
CreateErrorLabel(
this,
_error,
text,
st::boxPadding.left(),
_code->y() + _code->height() + st::boxLittleSkip);
if (!text.isEmpty()) {
_code->showError();
}
}
void ChangePhoneBox::EnterCode::sendCodeFail(const MTP::Error &error) {
_requestId = 0;
if (MTP::IsFloodError(error)) {
showError(tr::lng_flood_error(tr::now));
} else if (error.type() == qstr("PHONE_CODE_EMPTY") || error.type() == qstr("PHONE_CODE_INVALID")) {
} else if (error.type() == qstr("PHONE_CODE_EMPTY")
|| error.type() == qstr("PHONE_CODE_INVALID")) {
showError(tr::lng_bad_code(tr::now));
} else if (error.type() == qstr("PHONE_CODE_EXPIRED")
|| error.type() == qstr("PHONE_NUMBER_BANNED")) {
@@ -371,18 +423,25 @@ void ChangePhoneBox::EnterCode::sendCodeFail(const MTP::Error &error) {
}
}
ChangePhoneBox::ChangePhoneBox(QWidget*, not_null<Main::Session*> session)
: _session(session) {
ChangePhoneBox::ChangePhoneBox(
QWidget*,
not_null<Window::SessionController*> controller)
: _controller(controller) {
}
void ChangePhoneBox::prepare() {
const auto session = _session;
setTitle(tr::lng_change_phone_title());
addButton(tr::lng_change_phone_button(), [=] {
Ui::show(Box<ConfirmBox>(tr::lng_change_phone_warning(tr::now), [=] {
Ui::show(Box<EnterPhone>(session));
}));
addButton(tr::lng_change_phone_button(), [=, controller = _controller] {
auto callback = [=] {
controller->show(
Box<EnterPhone>(controller),
Ui::LayerOption::CloseOther);
};
controller->show(
Box<Ui::ConfirmBox>(
tr::lng_change_phone_warning(tr::now),
std::move(callback)),
Ui::LayerOption::CloseOther);
});
addButton(tr::lng_cancel(), [this] {
closeBox();
@@ -392,14 +451,22 @@ void ChangePhoneBox::prepare() {
this,
tr::lng_change_phone_about(Ui::Text::RichLangValue),
st::changePhoneDescription);
label->moveToLeft((st::boxWideWidth - label->width()) / 2, st::changePhoneDescriptionTop);
label->moveToLeft(
(st::boxWideWidth - label->width()) / 2,
st::changePhoneDescriptionTop);
setDimensions(st::boxWideWidth, label->bottomNoMargins() + st::boxLittleSkip);
setDimensions(
st::boxWideWidth,
label->bottomNoMargins() + st::boxLittleSkip);
}
void ChangePhoneBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
st::changePhoneIcon.paint(p, (width() - st::changePhoneIcon.width()) / 2, st::changePhoneIconTop, width());
st::changePhoneIcon.paint(
p,
(width() - st::changePhoneIcon.width()) / 2,
st::changePhoneIconTop,
width());
}

View File

@@ -9,13 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
namespace Main {
class Session;
} // namespace Main
namespace Window {
class SessionController;
} // namespace Window
class ChangePhoneBox : public Ui::BoxContent {
public:
ChangePhoneBox(QWidget*, not_null<Main::Session*> session);
ChangePhoneBox(QWidget*, not_null<Window::SessionController*> controller);
protected:
void prepare() override;
@@ -26,7 +26,7 @@ private:
class EnterPhone;
class EnterCode;
const not_null<Main::Session*> _session;
const not_null<Window::SessionController*> _controller;
};

View File

@@ -1,983 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "history/history.h"
#include "history/history_item.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/toast/toast.h"
#include "ui/image/image.h"
#include "ui/text/text_utilities.h"
#include "ui/empty_userpic.h"
#include "core/click_handler_types.h"
#include "window/window_session_controller.h"
#include "storage/localstorage.h"
#include "data/data_scheduled_messages.h"
#include "data/data_session.h"
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_histories.h"
#include "data/data_photo_media.h"
#include "data/data_changes.h"
#include "base/unixtime.h"
#include "history/view/controls/history_view_ttl_button.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "facades.h" // Ui::showChatsList
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace {
TextParseOptions kInformBoxTextOptions = {
(TextParseLinks
| TextParseMultiline
| TextParseMarkdown
| TextParseRichText), // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
TextParseOptions kMarkedTextBoxOptions = {
(TextParseLinks
| TextParseMultiline
| TextParseMarkdown
| TextParseRichText
| TextParseMentions
| TextParseHashtags), // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
[[nodiscard]] bool IsOldForPin(MsgId id, not_null<PeerData*> peer) {
const auto normal = peer->migrateToOrMe();
const auto migrated = normal->migrateFrom();
const auto top = Data::ResolveTopPinnedId(normal, migrated);
if (!top) {
return false;
} else if (peer == migrated) {
return top.channel || (id < top.msg);
} else if (migrated) {
return top.channel && (id < top.msg);
} else {
return (id < top.msg);
}
}
} // namespace
ConfirmBox::ConfirmBox(
QWidget*,
const QString &text,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(tr::lng_box_ok(tr::now))
, _cancelText(tr::lng_cancel(tr::now))
, _confirmStyle(st::defaultBoxButton)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(confirmText)
, _cancelText(tr::lng_cancel(tr::now))
, _confirmStyle(st::defaultBoxButton)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
QWidget*,
const TextWithEntities &text,
const QString &confirmText,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(confirmText)
, _cancelText(tr::lng_cancel(tr::now))
, _confirmStyle(st::defaultBoxButton)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const style::RoundButton &confirmStyle,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(confirmText)
, _cancelText(tr::lng_cancel(tr::now))
, _confirmStyle(confirmStyle)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const QString &cancelText,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(confirmText)
, _cancelText(cancelText)
, _confirmStyle(st::defaultBoxButton)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const style::RoundButton &confirmStyle,
const QString &cancelText,
ConfirmBox::ConfirmedCallback confirmedCallback,
FnMut<void()> cancelledCallback)
: _confirmText(confirmText)
, _cancelText(cancelText)
, _confirmStyle(st::defaultBoxButton)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(std::move(confirmedCallback))
, _cancelledCallback(std::move(cancelledCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
const InformBoxTag &,
const QString &text,
const QString &doneText,
Fn<void()> closedCallback)
: _confirmText(doneText)
, _confirmStyle(st::defaultBoxButton)
, _informative(true)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(generateInformCallback(closedCallback))
, _cancelledCallback(generateInformCallback(closedCallback)) {
init(text);
}
ConfirmBox::ConfirmBox(
const InformBoxTag &,
const TextWithEntities &text,
const QString &doneText,
Fn<void()> closedCallback)
: _confirmText(doneText)
, _confirmStyle(st::defaultBoxButton)
, _informative(true)
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
, _confirmedCallback(generateInformCallback(closedCallback))
, _cancelledCallback(generateInformCallback(closedCallback)) {
init(text);
}
FnMut<void()> ConfirmBox::generateInformCallback(
Fn<void()> closedCallback) {
return crl::guard(this, [=] {
closeBox();
if (closedCallback) {
closedCallback();
}
});
}
void ConfirmBox::init(const QString &text) {
_text.setText(
st::boxLabelStyle,
text,
_informative ? kInformBoxTextOptions : _textPlainOptions);
}
void ConfirmBox::init(const TextWithEntities &text) {
_text.setMarkedText(st::boxLabelStyle, text, kMarkedTextBoxOptions);
}
void ConfirmBox::prepare() {
addButton(
rpl::single(_confirmText),
[=] { confirmed(); },
_confirmStyle);
if (!_informative) {
addButton(
rpl::single(_cancelText),
[=] { _cancelled = true; closeBox(); });
}
boxClosing() | rpl::start_with_next([=] {
if (!_confirmed && (!_strictCancel || _cancelled)) {
if (auto callback = std::move(_cancelledCallback)) {
callback();
}
}
}, lifetime());
textUpdated();
}
void ConfirmBox::setMaxLineCount(int count) {
if (_maxLineCount != count) {
_maxLineCount = count;
textUpdated();
}
}
void ConfirmBox::textUpdated() {
_textWidth = st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right();
_textHeight = _text.countHeight(_textWidth);
if (_maxLineCount > 0) {
accumulate_min(_textHeight, _maxLineCount * st::boxLabelStyle.lineHeight);
}
setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom());
setMouseTracking(_text.hasLinks());
}
void ConfirmBox::confirmed() {
if (!_confirmed) {
_confirmed = true;
const auto confirmed = &_confirmedCallback;
if (const auto callbackPtr = std::get_if<1>(confirmed)) {
if (auto callback = base::take(*callbackPtr)) {
callback();
}
} else if (const auto callbackPtr = std::get_if<2>(confirmed)) {
if (auto callback = base::take(*callbackPtr)) {
const auto weak = Ui::MakeWeak(this);
callback(crl::guard(weak, [=] { closeBox(); }));
}
}
}
}
void ConfirmBox::mouseMoveEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
}
void ConfirmBox::mousePressEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
ClickHandler::pressed();
return BoxContent::mousePressEvent(e);
}
void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) {
_lastMousePos = e->globalPos();
updateHover();
if (const auto activated = ClickHandler::unpressed()) {
ActivateClickHandler(window(), activated, e->button());
crl::on_main(this, [=] {
closeBox();
});
return;
}
BoxContent::mouseReleaseEvent(e);
}
void ConfirmBox::leaveEventHook(QEvent *e) {
ClickHandler::clearActive(this);
}
void ConfirmBox::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
setCursor(active ? style::cur_pointer : style::cur_default);
update();
}
void ConfirmBox::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
update();
}
void ConfirmBox::updateLink() {
_lastMousePos = QCursor::pos();
updateHover();
}
void ConfirmBox::updateHover() {
auto m = mapFromGlobal(_lastMousePos);
auto state = _text.getStateLeft(m - QPoint(st::boxPadding.left(), st::boxPadding.top()), _textWidth, width());
ClickHandler::setActive(state.link, this);
}
void ConfirmBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
confirmed();
} else {
BoxContent::keyPressEvent(e);
}
}
void ConfirmBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
// draw box title / text
p.setPen(st::boxTextFg);
if (_maxLineCount > 0) {
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), _maxLineCount, style::al_left);
} else {
_text.drawLeft(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), style::al_left);
}
}
InformBox::InformBox(QWidget*, const QString &text, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, tr::lng_box_ok(tr::now), std::move(closedCallback)) {
}
InformBox::InformBox(QWidget*, const QString &text, const QString &doneText, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std::move(closedCallback)) {
}
InformBox::InformBox(QWidget*, const TextWithEntities &text, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, tr::lng_box_ok(tr::now), std::move(closedCallback)) {
}
InformBox::InformBox(QWidget*, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std::move(closedCallback)) {
}
MaxInviteBox::MaxInviteBox(QWidget*, not_null<ChannelData*> channel) : BoxContent()
, _channel(channel)
, _text(
st::boxLabelStyle,
tr::lng_participant_invite_sorry(
tr::now,
lt_count,
channel->session().serverConfig().chatSizeMax),
kInformBoxTextOptions,
(st::boxWidth
- st::boxPadding.left()
- st::defaultBox.buttonPadding.right())) {
}
void MaxInviteBox::prepare() {
setMouseTracking(true);
addButton(tr::lng_box_ok(), [=] { closeBox(); });
_textWidth = st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right();
_textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight);
setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxTextFont->height + st::boxTextFont->height * 2 + st::newGroupLinkPadding.bottom());
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());
}
void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) {
updateSelected(e->globalPos());
}
void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_linkOver) {
if (_channel->inviteLink().isEmpty()) {
_channel->session().api().inviteLinks().create(_channel);
} else {
QGuiApplication::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));
}
}
}
void MaxInviteBox::leaveEventHook(QEvent *e) {
updateSelected(QCursor::pos());
}
void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) {
QPoint p(mapFromGlobal(cursorGlobalPosition));
bool linkOver = _invitationLink.contains(p);
if (linkOver != _linkOver) {
_linkOver = linkOver;
update();
setCursor(_linkOver ? style::cur_pointer : style::cur_default);
}
}
void MaxInviteBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
// draw box title / text
p.setPen(st::boxTextFg);
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left);
QTextOption option(style::al_left);
option.setWrapMode(QTextOption::WrapAnywhere);
p.setFont(_linkOver ? st::defaultInputField.font->underline() : st::defaultInputField.font);
p.setPen(st::defaultLinkButton.color);
auto inviteLinkText = _channel->inviteLink().isEmpty() ? tr::lng_group_invite_create(tr::now) : _channel->inviteLink();
p.drawText(_invitationLink, inviteLinkText, option);
}
void MaxInviteBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_invitationLink = myrtlrect(st::boxPadding.left(), st::boxPadding.top() + _textHeight + st::boxTextFont->height, width() - st::boxPadding.left() - st::boxPadding.right(), 2 * st::boxTextFont->height);
}
PinMessageBox::PinMessageBox(
QWidget*,
not_null<PeerData*> peer,
MsgId msgId)
: _peer(peer)
, _api(&peer->session().mtp())
, _msgId(msgId)
, _pinningOld(IsOldForPin(msgId, peer))
, _text(
this,
(_pinningOld
? tr::lng_pinned_pin_old_sure(tr::now)
: (peer->isChat() || peer->isMegagroup())
? tr::lng_pinned_pin_sure_group(tr::now)
: tr::lng_pinned_pin_sure(tr::now)),
st::boxLabel) {
}
void PinMessageBox::prepare() {
addButton(tr::lng_pinned_pin(), [this] { pinMessage(); });
addButton(tr::lng_cancel(), [this] { closeBox(); });
if (_peer->isUser() && !_peer->isSelf()) {
_pinForPeer.create(
this,
tr::lng_pinned_also_for_other(
tr::now,
lt_user,
_peer->shortName()),
false,
st::defaultBoxCheckbox);
_checkbox = _pinForPeer;
} else if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) {
_notify.create(
this,
tr::lng_pinned_notify(tr::now),
true,
st::defaultBoxCheckbox);
_checkbox = _notify;
}
auto height = st::boxPadding.top() + _text->height() + st::boxPadding.bottom();
if (_checkbox) {
height += st::boxMediumSkip + _checkbox->heightNoMargins();
}
setDimensions(st::boxWidth, height);
}
void PinMessageBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
if (_checkbox) {
_checkbox->moveToLeft(st::boxPadding.left(), _text->y() + _text->height() + st::boxMediumSkip);
}
}
void PinMessageBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
pinMessage();
} else {
BoxContent::keyPressEvent(e);
}
}
void PinMessageBox::pinMessage() {
if (_requestId) return;
auto flags = MTPmessages_UpdatePinnedMessage::Flags(0);
if (_notify && !_notify->checked()) {
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_silent;
}
if (_pinForPeer && !_pinForPeer->checked()) {
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_pm_oneside;
}
_requestId = _api.request(MTPmessages_UpdatePinnedMessage(
MTP_flags(flags),
_peer->input,
MTP_int(_msgId)
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
Ui::hideLayer();
}).fail([=](const MTP::Error &error) {
Ui::hideLayer();
}).send();
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<HistoryItem*> item,
bool suggestModerateActions)
: _session(&item->history()->session())
, _ids(1, item->fullId()) {
if (suggestModerateActions) {
_moderateBan = item->suggestBanReport();
_moderateDeleteAll = item->suggestDeleteAllReport();
if (_moderateBan || _moderateDeleteAll) {
_moderateFrom = item->from()->asUser();
_moderateInChannel = item->history()->peer->asChannel();
}
}
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<Main::Session*> session,
MessageIdsList &&selected)
: _session(session)
, _ids(std::move(selected)) {
Expects(!_ids.empty());
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<PeerData*> peer,
bool justClear)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(justClear) {
}
void DeleteMessagesBox::prepare() {
auto details = TextWithEntities();
const auto appendDetails = [&](TextWithEntities &&text) {
details.append(qstr("\n\n")).append(std::move(text));
};
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
*deleteText = tr::lng_box_delete();
auto deleteStyle = &st::defaultBoxButton;
auto canDelete = true;
if (const auto peer = _wipeHistoryPeer) {
if (_wipeHistoryJustClear) {
const auto isChannel = peer->isBroadcast();
const auto isPublicGroup = peer->isMegagroup()
&& peer->asChannel()->isPublic();
if (isChannel || isPublicGroup) {
canDelete = false;
}
details.text = isChannel
? tr::lng_no_clear_history_channel(tr::now)
: isPublicGroup
? tr::lng_no_clear_history_group(tr::now)
: peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
: tr::lng_sure_delete_group_history(tr::now, lt_group, peer->name);
deleteStyle = &st::attentionBoxButton;
} else {
details.text = peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
: peer->isChat()
? tr::lng_sure_delete_and_exit(tr::now, lt_group, peer->name)
: peer->isMegagroup()
? tr::lng_sure_leave_group(tr::now)
: tr::lng_sure_leave_channel(tr::now);
if (!peer->isUser()) {
*deleteText = tr::lng_box_leave();
}
deleteStyle = &st::attentionBoxButton;
}
if (auto revoke = revokeText(peer)) {
_revoke.create(this, revoke->checkbox, false, st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
if (!peer->isUser() && !_wipeHistoryJustClear) {
_revoke->checkedValue(
) | rpl::start_with_next([=](bool revokeForAll) {
*deleteText = revokeForAll
? tr::lng_box_delete()
: tr::lng_box_leave();
}, _revoke->lifetime());
}
}
} else if (_moderateFrom) {
Assert(_moderateInChannel != nullptr);
details.text = tr::lng_selected_delete_sure_this(tr::now);
if (_moderateBan) {
_banUser.create(this, tr::lng_ban_user(tr::now), false, st::defaultBoxCheckbox);
}
_reportSpam.create(this, tr::lng_report_spam(tr::now), false, st::defaultBoxCheckbox);
if (_moderateDeleteAll) {
_deleteAll.create(this, tr::lng_delete_all_from(tr::now), false, st::defaultBoxCheckbox);
}
} else {
details.text = (_ids.size() == 1)
? tr::lng_selected_delete_sure_this(tr::now)
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
if (const auto peer = checkFromSinglePeer()) {
auto count = int(_ids.size());
if (hasScheduledMessages()) {
} else if (auto revoke = revokeText(peer)) {
_revoke.create(this, revoke->checkbox, false, st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
} else if (peer->isChannel()) {
if (peer->isMegagroup()) {
appendDetails({ tr::lng_delete_for_everyone_hint(tr::now, lt_count, count) });
}
} else if (peer->isChat()) {
appendDetails({ tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count) });
} else if (!peer->isSelf()) {
appendDetails({ tr::lng_delete_for_me_hint(tr::now, lt_count, count) });
}
}
}
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
if (_wipeHistoryJustClear
&& _wipeHistoryPeer
&& ((_wipeHistoryPeer->isUser()
&& !_wipeHistoryPeer->isSelf()
&& !_wipeHistoryPeer->isNotificationsUser())
|| (_wipeHistoryPeer->isChat()
&& _wipeHistoryPeer->asChat()->canDeleteMessages())
|| (_wipeHistoryPeer->isChannel()
&& _wipeHistoryPeer->asChannel()->canDeleteMessages()))) {
_wipeHistoryPeer->updateFull();
_autoDeleteSettings.create(
this,
(_wipeHistoryPeer->messagesTTL()
? tr::lng_edit_auto_delete_settings(tr::now)
: tr::lng_enable_auto_delete(tr::now)),
st::boxLinkButton);
_autoDeleteSettings->setClickedCallback([=] {
getDelegate()->show(
Box(
HistoryView::Controls::AutoDeleteSettingsBox,
_wipeHistoryPeer),
Ui::LayerOption(0));
});
}
if (canDelete) {
addButton(
deleteText->value(),
[=] { deleteAndClear(); },
*deleteStyle);
addButton(tr::lng_cancel(), [=] { closeBox(); });
} else {
addButton(tr::lng_about_done(), [=] { closeBox(); });
}
auto fullHeight = st::boxPadding.top() + _text->height() + st::boxPadding.bottom();
if (_moderateFrom) {
fullHeight += st::boxMediumSkip;
if (_banUser) {
fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
}
fullHeight += _reportSpam->heightNoMargins();
if (_deleteAll) {
fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
}
} else if (_revoke) {
fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
}
if (_autoDeleteSettings) {
fullHeight += st::boxMediumSkip + _autoDeleteSettings->height() + st::boxLittleSkip;
}
setDimensions(st::boxWidth, fullHeight);
}
bool DeleteMessagesBox::hasScheduledMessages() const {
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
if (item->isScheduled()) {
return true;
}
}
}
return false;
}
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
auto result = (PeerData*)nullptr;
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
const auto peer = item->history()->peer;
if (!result) {
result = peer;
} else if (result != peer) {
return nullptr;
}
}
}
return result;
}
auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
-> std::optional<RevokeConfig> {
auto result = RevokeConfig();
if (peer == _wipeHistoryPeer) {
if (!peer->canRevokeFullHistory()) {
return std::nullopt;
} else if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
user->firstName);
} else if (_wipeHistoryJustClear) {
return std::nullopt;
} else {
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
}
const auto items = ranges::views::all(
_ids
) | ranges::views::transform([&](FullMsgId id) {
return peer->owner().message(id);
}) | ranges::views::filter([](HistoryItem *item) {
return (item != nullptr);
}) | ranges::to_vector;
if (items.size() != _ids.size()) {
// We don't have information about all messages.
return std::nullopt;
}
const auto now = base::unixtime::now();
const auto canRevoke = [&](HistoryItem * item) {
return item->canDeleteForEveryone(now);
};
const auto cannotRevoke = [&](HistoryItem *item) {
return !item->canDeleteForEveryone(now);
};
const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
auto outgoing = items | ranges::views::filter(&HistoryItem::out);
const auto canRevokeOutgoingCount = canRevokeAll
? -1
: ranges::count_if(outgoing, canRevoke);
if (canRevokeAll) {
if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
user->firstName);
} else {
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
} else if (canRevokeOutgoingCount > 0) {
result.checkbox = tr::lng_delete_for_other_my(tr::now);
if (const auto user = peer->asUser()) {
if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_user_one(
tr::now,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_user(
tr::now,
lt_count,
canRevokeOutgoingCount,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
}
} else if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_group_one(
tr::now,
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_group(
tr::now,
lt_count,
canRevokeOutgoingCount,
Ui::Text::WithEntities);
}
return result;
}
return std::nullopt;
}
void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
auto top = _text->bottomNoMargins() + st::boxMediumSkip;
if (_moderateFrom) {
if (_banUser) {
_banUser->moveToLeft(st::boxPadding.left(), top);
top += _banUser->heightNoMargins() + st::boxLittleSkip;
}
_reportSpam->moveToLeft(st::boxPadding.left(), top);
top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
if (_deleteAll) {
_deleteAll->moveToLeft(st::boxPadding.left(), top);
top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
}
} else if (_revoke) {
const auto availableWidth = width() - 2 * st::boxPadding.left();
_revoke->resizeToNaturalWidth(availableWidth);
_revoke->moveToLeft(st::boxPadding.left(), top);
top += _revoke->heightNoMargins() + st::boxLittleSkip;
}
if (_autoDeleteSettings) {
top += st::boxMediumSkip - st::boxLittleSkip;
_autoDeleteSettings->moveToLeft(st::boxPadding.left(), top);
}
}
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
// Don't make the clearing history so easy.
if (!_wipeHistoryPeer) {
deleteAndClear();
}
} else {
BoxContent::keyPressEvent(e);
}
}
void DeleteMessagesBox::deleteAndClear() {
const auto revoke = _revoke ? _revoke->checked() : false;
if (const auto peer = _wipeHistoryPeer) {
const auto justClear = _wipeHistoryJustClear;
closeBox();
if (justClear) {
peer->session().api().clearHistory(peer, revoke);
} else {
for (const auto &controller : peer->session().windows()) {
if (controller->activeChatCurrent().peer() == peer) {
Ui::showChatsList(&peer->session());
}
}
// Don't delete old history by default,
// because Android app doesn't.
//
//if (const auto from = peer->migrateFrom()) {
// peer->session().api().deleteConversation(from, false);
//}
peer->session().api().deleteConversation(peer, revoke);
}
return;
}
if (_moderateFrom) {
if (_banUser && _banUser->checked()) {
_moderateInChannel->session().api().kickParticipant(
_moderateInChannel,
_moderateFrom,
ChatRestrictionsInfo());
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(
MTPchannels_ReportSpam(
_moderateInChannel->inputChannel,
_moderateFrom->inputUser,
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
).send();
}
if (_deleteAll && _deleteAll->checked()) {
_moderateInChannel->session().api().deleteAllFromUser(
_moderateInChannel,
_moderateFrom);
}
}
if (_deleteConfirmedCallback) {
_deleteConfirmedCallback();
}
// deleteMessages can initiate closing of the current section,
// which will cause this box to be destroyed.
const auto session = _session;
const auto weak = Ui::MakeWeak(this);
session->data().histories().deleteMessages(_ids, revoke);
if (const auto strong = weak.data()) {
strong->closeBox();
}
session->data().sendHistoryChangeNotifications();
}
ConfirmDontWarnBox::ConfirmDontWarnBox(
QWidget*,
rpl::producer<TextWithEntities> text,
const QString &checkbox,
rpl::producer<QString> confirm,
FnMut<void(bool)> callback)
: _confirm(std::move(confirm))
, _content(setupContent(std::move(text), checkbox, std::move(callback))) {
}
void ConfirmDontWarnBox::prepare() {
setDimensionsToContent(st::boxWidth, _content);
addButton(std::move(_confirm), [=] { _callback(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
}
not_null<Ui::RpWidget*> ConfirmDontWarnBox::setupContent(
rpl::producer<TextWithEntities> text,
const QString &checkbox,
FnMut<void(bool)> callback) {
const auto result = Ui::CreateChild<Ui::VerticalLayout>(this);
result->add(
object_ptr<Ui::FlatLabel>(
result,
std::move(text),
st::boxLabel),
st::boxPadding);
const auto control = result->add(
object_ptr<Ui::Checkbox>(
result,
checkbox,
false,
st::defaultBoxCheckbox),
style::margins(
st::boxPadding.left(),
st::boxPadding.bottom(),
st::boxPadding.right(),
st::boxPadding.bottom()));
_callback = [=, callback = std::move(callback)]() mutable {
const auto checked = control->checked();
auto local = std::move(callback);
closeBox();
local(checked);
};
return result;
}

View File

@@ -1,275 +0,0 @@
/*
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
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
namespace Data {
class PhotoMedia;
class CloudImageView;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class Checkbox;
class FlatLabel;
class EmptyUserpic;
class LinkButton;
} // namespace Ui
class InformBox;
class ConfirmBox : public Ui::BoxContent, public ClickHandlerHost {
public:
using ConfirmedCallback = std::variant<
v::null_t,
FnMut<void()>,
FnMut<void(Fn<void()>)>>;
ConfirmBox(
QWidget*,
const QString &text,
ConfirmedCallback confirmedCallback = FnMut<void()>(),
FnMut<void()> cancelledCallback = FnMut<void()>());
ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
ConfirmedCallback confirmedCallback = FnMut<void()>(),
FnMut<void()> cancelledCallback = FnMut<void()>());
ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const style::RoundButton &confirmStyle,
ConfirmedCallback confirmedCallback = FnMut<void()>(),
FnMut<void()> cancelledCallback = FnMut<void()>());
ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const QString &cancelText,
ConfirmedCallback confirmedCallback = FnMut<void()>(),
FnMut<void()> cancelledCallback = FnMut<void()>());
ConfirmBox(
QWidget*,
const QString &text,
const QString &confirmText,
const style::RoundButton &confirmStyle,
const QString &cancelText,
ConfirmedCallback confirmedCallback = FnMut<void()>(),
FnMut<void()> cancelledCallback = FnMut<void()>());
ConfirmBox(
QWidget*,
const TextWithEntities &text,
const QString &confirmText,
ConfirmedCallback confirmedCallback = v::null,
FnMut<void()> cancelledCallback = nullptr);
void updateLink();
// If strict cancel is set the cancelledCallback is only called if the cancel button was pressed.
void setStrictCancel(bool strictCancel) {
_strictCancel = strictCancel;
}
void setMaxLineCount(int count);
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
protected:
void prepare() override;
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private:
struct InformBoxTag {
};
ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, Fn<void()> closedCallback);
ConfirmBox(const InformBoxTag &, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback);
FnMut<void()> generateInformCallback(Fn<void()> closedCallback);
friend class InformBox;
void confirmed();
void init(const QString &text);
void init(const TextWithEntities &text);
void textUpdated();
void updateHover();
QString _confirmText;
QString _cancelText;
const style::RoundButton &_confirmStyle;
bool _informative = false;
Ui::Text::String _text;
int _textWidth = 0;
int _textHeight = 0;
int _maxLineCount = 16;
QPoint _lastMousePos;
bool _confirmed = false;
bool _cancelled = false;
bool _strictCancel = false;
ConfirmBox::ConfirmedCallback _confirmedCallback;
FnMut<void()> _cancelledCallback;
};
class InformBox : public ConfirmBox {
public:
InformBox(QWidget*, const QString &text, Fn<void()> closedCallback = nullptr);
InformBox(QWidget*, const QString &text, const QString &doneText, Fn<void()> closedCallback = nullptr);
InformBox(QWidget*, const TextWithEntities &text, Fn<void()> closedCallback = nullptr);
InformBox(QWidget*, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback = nullptr);
};
class MaxInviteBox final : public Ui::BoxContent {
public:
MaxInviteBox(QWidget*, not_null<ChannelData*> channel);
protected:
void prepare() override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private:
void updateSelected(const QPoint &cursorGlobalPosition);
not_null<ChannelData*> _channel;
Ui::Text::String _text;
int32 _textWidth, _textHeight;
QRect _invitationLink;
bool _linkOver = false;
QPoint _lastMousePos;
};
class PinMessageBox final : public Ui::BoxContent {
public:
PinMessageBox(QWidget*, not_null<PeerData*> peer, MsgId msgId);
protected:
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void pinMessage();
const not_null<PeerData*> _peer;
MTP::Sender _api;
MsgId _msgId = 0;
bool _pinningOld = false;
object_ptr<Ui::FlatLabel> _text;
object_ptr<Ui::Checkbox> _notify = { nullptr };
object_ptr<Ui::Checkbox> _pinForPeer = { nullptr };
QPointer<Ui::Checkbox> _checkbox;
mtpRequestId _requestId = 0;
};
class DeleteMessagesBox final : public Ui::BoxContent {
public:
DeleteMessagesBox(
QWidget*,
not_null<HistoryItem*> item,
bool suggestModerateActions);
DeleteMessagesBox(
QWidget*,
not_null<Main::Session*> session,
MessageIdsList &&selected);
DeleteMessagesBox(QWidget*, not_null<PeerData*> peer, bool justClear);
void setDeleteConfirmedCallback(Fn<void()> callback) {
_deleteConfirmedCallback = std::move(callback);
}
protected:
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
struct RevokeConfig {
QString checkbox;
TextWithEntities description;
};
void deleteAndClear();
[[nodiscard]] PeerData *checkFromSinglePeer() const;
[[nodiscard]] bool hasScheduledMessages() const;
[[nodiscard]] std::optional<RevokeConfig> revokeText(
not_null<PeerData*> peer) const;
const not_null<Main::Session*> _session;
PeerData * const _wipeHistoryPeer = nullptr;
const bool _wipeHistoryJustClear = false;
const MessageIdsList _ids;
UserData *_moderateFrom = nullptr;
ChannelData *_moderateInChannel = nullptr;
bool _moderateBan = false;
bool _moderateDeleteAll = false;
object_ptr<Ui::FlatLabel> _text = { nullptr };
object_ptr<Ui::Checkbox> _revoke = { nullptr };
object_ptr<Ui::Checkbox> _banUser = { nullptr };
object_ptr<Ui::Checkbox> _reportSpam = { nullptr };
object_ptr<Ui::Checkbox> _deleteAll = { nullptr };
object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr };
Fn<void()> _deleteConfirmedCallback;
};
class ConfirmDontWarnBox : public Ui::BoxContent {
public:
ConfirmDontWarnBox(
QWidget*,
rpl::producer<TextWithEntities> text,
const QString &checkbox,
rpl::producer<QString> confirm,
FnMut<void(bool)> callback);
protected:
void prepare() override;
private:
not_null<Ui::RpWidget*> setupContent(
rpl::producer<TextWithEntities> text,
const QString &checkbox,
FnMut<void(bool)> callback);
rpl::producer<QString> _confirm;
FnMut<void()> _callback;
not_null<Ui::RpWidget*> _content;
};

View File

@@ -1,416 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/confirm_phone_box.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_utilities.h"
#include "core/click_handler_types.h" // UrlClickHandler
#include "base/qthelp_url.h" // qthelp::url_encode
#include "base/platform/base_platform_info.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "lang/lang_keys.h"
#include "mtproto/facade.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
namespace {
object_ptr<ConfirmPhoneBox> CurrentConfirmPhoneBox = { nullptr };
void SendToBannedHelp(const QString &phone) {
const auto version = QString::fromLatin1(AppVersionStr)
+ (cAlphaVersion()
? qsl(" alpha %1").arg(cAlphaVersion())
: (AppBetaVersion ? " beta" : ""));
const auto subject = qsl("Banned phone number: ") + phone;
const auto body = qsl("\
I'm trying to use my mobile phone number: ") + phone + qsl("\n\
But Telegram says it's banned. Please help.\n\
\n\
App version: ") + version + qsl("\n\
OS version: ") + Platform::SystemVersionPretty() + qsl("\n\
Locale: ") + Platform::SystemLanguage();
const auto url = "mailto:?to="
+ qthelp::url_encode("login@stel.com")
+ "&subject="
+ qthelp::url_encode(subject)
+ "&body="
+ qthelp::url_encode(body);
UrlClickHandler::Open(url);
}
} // namespace
void ShowPhoneBannedError(const QString &phone) {
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto close = [=] {
if (*box) {
(*box)->closeBox();
}
};
*box = Ui::show(Box<ConfirmBox>(
tr::lng_signin_banned_text(tr::now),
tr::lng_box_ok(tr::now),
tr::lng_signin_banned_help(tr::now),
close,
[=] { SendToBannedHelp(phone); close(); }));
}
SentCodeField::SentCodeField(
QWidget *parent,
const style::InputField &st,
rpl::producer<QString> placeholder,
const QString &val)
: Ui::InputField(parent, st, std::move(placeholder), val) {
connect(this, &Ui::InputField::changed, [this] { fix(); });
}
void SentCodeField::setAutoSubmit(int length, Fn<void()> submitCallback) {
_autoSubmitLength = length;
_submitCallback = std::move(submitCallback);
}
void SentCodeField::setChangedCallback(Fn<void()> changedCallback) {
_changedCallback = std::move(changedCallback);
}
QString SentCodeField::getDigitsOnly() const {
return QString(
getLastText()
).remove(
QRegularExpression("[^\\d]")
);
}
void SentCodeField::fix() {
if (_fixing) return;
_fixing = true;
auto newText = QString();
const auto now = getLastText();
auto oldPos = textCursor().position();
auto newPos = -1;
auto oldLen = now.size();
auto digitCount = 0;
for (const auto &ch : now) {
if (ch.isDigit()) {
++digitCount;
}
}
if (_autoSubmitLength > 0 && digitCount > _autoSubmitLength) {
digitCount = _autoSubmitLength;
}
auto strict = (_autoSubmitLength > 0)
&& (digitCount == _autoSubmitLength);
newText.reserve(oldLen);
int i = 0;
for (const auto &ch : now) {
if (i++ == oldPos) {
newPos = newText.length();
}
if (ch.isDigit()) {
if (!digitCount--) {
break;
}
newText += ch;
if (strict && !digitCount) {
break;
}
} else if (ch == '-') {
newText += ch;
}
}
if (newPos < 0) {
newPos = newText.length();
}
if (newText != now) {
setText(newText);
setCursorPosition(newPos);
}
_fixing = false;
if (_changedCallback) {
_changedCallback();
}
if (strict && _submitCallback) {
_submitCallback();
}
}
SentCodeCall::SentCodeCall(
FnMut<void()> callCallback,
Fn<void()> updateCallback)
: _call(std::move(callCallback))
, _update(std::move(updateCallback)) {
_timer.setCallback([=] {
if (_status.state == State::Waiting) {
if (--_status.timeout <= 0) {
_status.state = State::Calling;
_timer.cancel();
if (_call) {
_call();
}
}
}
if (_update) {
_update();
}
});
}
void SentCodeCall::setStatus(const Status &status) {
_status = status;
if (_status.state == State::Waiting) {
_timer.callEach(1000);
}
}
QString SentCodeCall::getText() const {
switch (_status.state) {
case State::Waiting: {
if (_status.timeout >= 3600) {
return tr::lng_code_call(tr::now, lt_minutes, qsl("%1:%2").arg(_status.timeout / 3600).arg((_status.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0')));
}
return tr::lng_code_call(tr::now, lt_minutes, QString::number(_status.timeout / 60), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0')));
} break;
case State::Calling: return tr::lng_code_calling(tr::now);
case State::Called: return tr::lng_code_called(tr::now);
}
return QString();
}
void ConfirmPhoneBox::Start(
not_null<Main::Session*> session,
const QString &phone,
const QString &hash) {
if (CurrentConfirmPhoneBox
&& (CurrentConfirmPhoneBox->getPhone() != phone
|| &CurrentConfirmPhoneBox->session() != session)) {
CurrentConfirmPhoneBox.destroyDelayed();
}
if (!CurrentConfirmPhoneBox) {
CurrentConfirmPhoneBox = Box<ConfirmPhoneBox>(session, phone, hash);
}
CurrentConfirmPhoneBox->checkPhoneAndHash();
}
ConfirmPhoneBox::ConfirmPhoneBox(
QWidget*,
not_null<Main::Session*> session,
const QString &phone,
const QString &hash)
: _session(session)
, _api(&session->mtp())
, _phone(phone)
, _hash(hash)
, _call([this] { sendCall(); }, [this] { update(); }) {
}
void ConfirmPhoneBox::sendCall() {
_api.request(MTPauth_ResendCode(
MTP_string(_phone),
MTP_string(_phoneHash)
)).done([=](const MTPauth_SentCode &result) {
callDone(result);
}).send();
}
void ConfirmPhoneBox::checkPhoneAndHash() {
if (_sendCodeRequestId) {
return;
}
_sendCodeRequestId = _api.request(MTPaccount_SendConfirmPhoneCode(
MTP_string(_hash),
MTP_codeSettings(MTP_flags(0))
)).done([=](const MTPauth_SentCode &result) {
sendCodeDone(result);
}).fail([=](const MTP::Error &error) {
sendCodeFail(error);
}).handleFloodErrors().send();
}
void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) {
result.match([&](const MTPDauth_sentCode &data) {
_sendCodeRequestId = 0;
_sentCodeLength = data.vtype().match([&](const MTPDauth_sentCodeTypeApp &data) {
LOG(("Error: should not be in-app code!"));
return 0;
}, [&](const MTPDauth_sentCodeTypeSms &data) {
return data.vlength().v;
}, [&](const MTPDauth_sentCodeTypeCall &data) {
return data.vlength().v;
}, [&](const MTPDauth_sentCodeTypeFlashCall &data) {
LOG(("Error: should not be flashcall!"));
return 0;
});
_phoneHash = qs(data.vphone_code_hash());
if (const auto nextType = data.vnext_type()) {
if (nextType->type() == mtpc_auth_codeTypeCall) {
_call.setStatus({ SentCodeCall::State::Waiting, data.vtimeout().value_or(60) });
}
}
launch();
});
}
void ConfirmPhoneBox::sendCodeFail(const MTP::Error &error) {
auto errorText = Lang::Hard::ServerError();
if (MTP::IsFloodError(error)) {
errorText = tr::lng_flood_error(tr::now);
} else if (error.code() == 400) {
errorText = tr::lng_confirm_phone_link_invalid(tr::now);
}
_sendCodeRequestId = 0;
Ui::show(Box<InformBox>(errorText));
if (this == CurrentConfirmPhoneBox) {
CurrentConfirmPhoneBox.destroyDelayed();
} else {
deleteLater();
}
}
void ConfirmPhoneBox::launch() {
if (!CurrentConfirmPhoneBox) return;
Ui::show(std::move(CurrentConfirmPhoneBox));
}
void ConfirmPhoneBox::prepare() {
_about.create(
this,
tr::lng_confirm_phone_about(
lt_phone,
rpl::single(Ui::Text::Bold(Ui::FormatPhone(_phone))),
Ui::Text::WithEntities),
st::confirmPhoneAboutLabel);
_code.create(this, st::confirmPhoneCodeField, tr::lng_code_ph());
_code->setAutoSubmit(_sentCodeLength, [=] { sendCode(); });
_code->setChangedCallback([=] { showError(QString()); });
setTitle(tr::lng_confirm_phone_title());
addButton(tr::lng_confirm_phone_send(), [=] { sendCode(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip);
connect(_code, &Ui::InputField::submitted, [=] { sendCode(); });
showChildren();
}
void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) {
_call.callDone();
}
void ConfirmPhoneBox::sendCode() {
if (_sendCodeRequestId) {
return;
}
const auto code = _code->getDigitsOnly();
if (code.isEmpty()) {
_code->showError();
return;
}
_code->setDisabled(true);
setFocus();
showError(QString());
_sendCodeRequestId = _api.request(MTPaccount_ConfirmPhone(
MTP_string(_phoneHash),
MTP_string(code)
)).done([=](const MTPBool &result) {
confirmDone(result);
}).fail([=](const MTP::Error &error) {
confirmFail(error);
}).handleFloodErrors().send();
}
void ConfirmPhoneBox::confirmDone(const MTPBool &result) {
_sendCodeRequestId = 0;
Ui::show(Box<InformBox>(tr::lng_confirm_phone_success(tr::now, lt_phone, Ui::FormatPhone(_phone))));
}
void ConfirmPhoneBox::confirmFail(const MTP::Error &error) {
auto errorText = Lang::Hard::ServerError();
if (MTP::IsFloodError(error)) {
errorText = tr::lng_flood_error(tr::now);
} else {
auto &errorType = error.type();
if (errorType == qstr("PHONE_CODE_EMPTY") || errorType == qstr("PHONE_CODE_INVALID")) {
errorText = tr::lng_bad_code(tr::now);
}
}
_sendCodeRequestId = 0;
_code->setDisabled(false);
_code->setFocus();
showError(errorText);
}
void ConfirmPhoneBox::showError(const QString &error) {
_error = error;
if (!_error.isEmpty()) {
_code->showError();
}
update();
}
void ConfirmPhoneBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
p.setFont(st::boxTextFont);
auto callText = _call.getText();
if (!callText.isEmpty()) {
p.setPen(st::usernameDefaultFg);
auto callTextRectLeft = st::usernamePadding.left();
auto callTextRectTop = _about->y() + _about->height();
auto callTextRectWidth = width() - 2 * st::usernamePadding.left();
auto callTextRect = QRect(callTextRectLeft, callTextRectTop, callTextRectWidth, st::usernameSkip);
p.drawText(callTextRect, callText, style::al_left);
}
auto errorText = _error;
if (errorText.isEmpty()) {
p.setPen(st::usernameDefaultFg);
errorText = tr::lng_confirm_phone_enter_code(tr::now);
} else {
p.setPen(st::boxTextFgError);
}
auto errorTextRectLeft = st::usernamePadding.left();
auto errorTextRectTop = _code->y() + _code->height();
auto errorTextRectWidth = width() - 2 * st::usernamePadding.left();
auto errorTextRect = QRect(errorTextRectLeft, errorTextRectTop, errorTextRectWidth, st::usernameSkip);
p.drawText(errorTextRect, errorText, style::al_left);
}
void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height());
_code->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top());
_about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip);
}
void ConfirmPhoneBox::setInnerFocus() {
_code->setFocusFast();
}

View File

@@ -1,157 +0,0 @@
/*
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
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "ui/widgets/input_fields.h"
#include "mtproto/sender.h"
namespace Ui {
class InputField;
class FlatLabel;
} // namespace Ui
namespace Main {
class Session;
} // namespace Main
void ShowPhoneBannedError(const QString &phone);
class SentCodeField : public Ui::InputField {
public:
SentCodeField(
QWidget *parent,
const style::InputField &st,
rpl::producer<QString> placeholder = nullptr,
const QString &val = QString());
void setAutoSubmit(int length, Fn<void()> submitCallback);
void setChangedCallback(Fn<void()> changedCallback);
QString getDigitsOnly() const;
private:
void fix();
// Flag for not calling onTextChanged() recursively.
bool _fixing = false;
int _autoSubmitLength = 0;
Fn<void()> _submitCallback;
Fn<void()> _changedCallback;
};
class SentCodeCall {
public:
SentCodeCall(
FnMut<void()> callCallback,
Fn<void()> updateCallback);
enum class State {
Waiting,
Calling,
Called,
Disabled,
};
struct Status {
Status() {
}
Status(State state, int timeout) : state(state), timeout(timeout) {
}
State state = State::Disabled;
int timeout = 0;
};
void setStatus(const Status &status);
void callDone() {
if (_status.state == State::Calling) {
_status.state = State::Called;
if (_update) {
_update();
}
}
}
QString getText() const;
private:
Status _status;
base::Timer _timer;
FnMut<void()> _call;
Fn<void()> _update;
};
class ConfirmPhoneBox final : public Ui::BoxContent {
public:
static void Start(
not_null<Main::Session*> session,
const QString &phone,
const QString &hash);
[[nodiscard]] Main::Session &session() const {
return *_session;
}
protected:
void prepare() override;
void setInnerFocus() override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
ConfirmPhoneBox(
QWidget*,
not_null<Main::Session*> session,
const QString &phone,
const QString &hash);
friend class object_ptr<ConfirmPhoneBox>;
void sendCode();
void sendCall();
void checkPhoneAndHash();
void sendCodeDone(const MTPauth_SentCode &result);
void sendCodeFail(const MTP::Error &error);
void callDone(const MTPauth_SentCode &result);
void confirmDone(const MTPBool &result);
void confirmFail(const MTP::Error &error);
QString getPhone() const {
return _phone;
}
void launch();
void showError(const QString &error);
const not_null<Main::Session*> _session;
MTP::Sender _api;
mtpRequestId _sendCodeRequestId = 0;
// _hash from the link for account.sendConfirmPhoneCode call.
// _phoneHash from auth.sentCode for account.confirmPhone call.
QString _phone, _hash;
QString _phoneHash;
// If we receive the code length, we autosubmit _code field when enough symbols is typed.
int _sentCodeLength = 0;
mtpRequestId _checkCodeRequestId = 0;
object_ptr<Ui::FlatLabel> _about = { nullptr };
object_ptr<SentCodeField> _code = { nullptr };
QString _error;
SentCodeCall _call;
};

View File

@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/connection_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "base/qthelp_url.h"
@@ -74,7 +74,7 @@ void HostInput::correctValue(
QString newText;
int newCursor = nowCursor;
newText.reserve(now.size());
for (auto i = 0, l = now.size(); i < l; ++i) {
for (auto i = 0, l = int(now.size()); i < l; ++i) {
if (now[i] == ',') {
newText.append('.');
} else {
@@ -120,7 +120,7 @@ void Base64UrlInput::correctValue(
QString newText;
newText.reserve(now.size());
auto newPos = nowCursor;
for (auto i = 0, l = now.size(); i < l; ++i) {
for (auto i = 0, l = int(now.size()); i < l; ++i) {
const auto ch = now[i];
if ((ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
@@ -537,8 +537,8 @@ void ProxyRow::showMenu() {
_deleteClicks.fire({});
});
}
const auto parentTopLeft = window()->mapToGlobal({ 0, 0 });
const auto buttonTopLeft = _menuToggle->mapToGlobal({ 0, 0 });
const auto parentTopLeft = window()->mapToGlobal(QPoint());
const auto buttonTopLeft = _menuToggle->mapToGlobal(QPoint());
const auto parent = QRect(parentTopLeft, window()->size());
const auto button = QRect(buttonTopLeft, _menuToggle->size());
const auto bottom = button.y()
@@ -1131,13 +1131,13 @@ void ProxiesBoxController::ShowApplyConfirmation(
close();
};
Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
tr::lng_sure_enable(tr::now),
std::move(callback)),
Ui::LayerOption::KeepOther);
} else {
Ui::show(Box<InformBox>(
Ui::show(Box<Ui::InformBox>(
(proxy.status() == ProxyData::Status::Unsupported
? tr::lng_proxy_unsupported(tr::now)
: tr::lng_proxy_invalid(tr::now))));

View File

@@ -0,0 +1,459 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/delete_messages_box.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/controls/history_view_ttl_button.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "window/window_session_controller.h"
#include "facades.h" // Ui::showChatsList
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<HistoryItem*> item,
bool suggestModerateActions)
: _session(&item->history()->session())
, _ids(1, item->fullId()) {
if (suggestModerateActions) {
_moderateBan = item->suggestBanReport();
_moderateDeleteAll = item->suggestDeleteAllReport();
if (_moderateBan || _moderateDeleteAll) {
_moderateFrom = item->from()->asUser();
_moderateInChannel = item->history()->peer->asChannel();
}
}
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<Main::Session*> session,
MessageIdsList &&selected)
: _session(session)
, _ids(std::move(selected)) {
Expects(!_ids.empty());
}
DeleteMessagesBox::DeleteMessagesBox(
QWidget*,
not_null<PeerData*> peer,
bool justClear)
: _session(&peer->session())
, _wipeHistoryPeer(peer)
, _wipeHistoryJustClear(justClear) {
}
void DeleteMessagesBox::prepare() {
auto details = TextWithEntities();
const auto appendDetails = [&](TextWithEntities &&text) {
details.append(qstr("\n\n")).append(std::move(text));
};
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
*deleteText = tr::lng_box_delete();
auto deleteStyle = &st::defaultBoxButton;
auto canDelete = true;
if (const auto peer = _wipeHistoryPeer) {
if (_wipeHistoryJustClear) {
const auto isChannel = peer->isBroadcast();
const auto isPublicGroup = peer->isMegagroup()
&& peer->asChannel()->isPublic();
if (isChannel || isPublicGroup) {
canDelete = false;
}
details.text = isChannel
? tr::lng_no_clear_history_channel(tr::now)
: isPublicGroup
? tr::lng_no_clear_history_group(tr::now)
: peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
: tr::lng_sure_delete_group_history(
tr::now,
lt_group,
peer->name);
deleteStyle = &st::attentionBoxButton;
} else {
details.text = peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
: peer->isChat()
? tr::lng_sure_delete_and_exit(tr::now, lt_group, peer->name)
: peer->isMegagroup()
? tr::lng_sure_leave_group(tr::now)
: tr::lng_sure_leave_channel(tr::now);
if (!peer->isUser()) {
*deleteText = tr::lng_box_leave();
}
deleteStyle = &st::attentionBoxButton;
}
if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
false,
st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
if (!peer->isUser() && !_wipeHistoryJustClear) {
_revoke->checkedValue(
) | rpl::start_with_next([=](bool revokeForAll) {
*deleteText = revokeForAll
? tr::lng_box_delete()
: tr::lng_box_leave();
}, _revoke->lifetime());
}
}
} else if (_moderateFrom) {
Assert(_moderateInChannel != nullptr);
details.text = tr::lng_selected_delete_sure_this(tr::now);
if (_moderateBan) {
_banUser.create(
this,
tr::lng_ban_user(tr::now),
false,
st::defaultBoxCheckbox);
}
_reportSpam.create(
this,
tr::lng_report_spam(tr::now),
false,
st::defaultBoxCheckbox);
if (_moderateDeleteAll) {
_deleteAll.create(
this,
tr::lng_delete_all_from(tr::now),
false,
st::defaultBoxCheckbox);
}
} else {
details.text = (_ids.size() == 1)
? tr::lng_selected_delete_sure_this(tr::now)
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
if (const auto peer = checkFromSinglePeer()) {
auto count = int(_ids.size());
if (hasScheduledMessages()) {
} else if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
false,
st::defaultBoxCheckbox);
appendDetails(std::move(revoke->description));
} else if (peer->isChannel()) {
if (peer->isMegagroup()) {
appendDetails({
tr::lng_delete_for_everyone_hint(
tr::now,
lt_count,
count)
});
}
} else if (peer->isChat()) {
appendDetails({
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
});
} else if (!peer->isSelf()) {
appendDetails({
tr::lng_delete_for_me_hint(tr::now, lt_count, count)
});
}
}
}
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
if (_wipeHistoryJustClear
&& _wipeHistoryPeer
&& ((_wipeHistoryPeer->isUser()
&& !_wipeHistoryPeer->isSelf()
&& !_wipeHistoryPeer->isNotificationsUser())
|| (_wipeHistoryPeer->isChat()
&& _wipeHistoryPeer->asChat()->canDeleteMessages())
|| (_wipeHistoryPeer->isChannel()
&& _wipeHistoryPeer->asChannel()->canDeleteMessages()))) {
_wipeHistoryPeer->updateFull();
_autoDeleteSettings.create(
this,
(_wipeHistoryPeer->messagesTTL()
? tr::lng_edit_auto_delete_settings(tr::now)
: tr::lng_enable_auto_delete(tr::now)),
st::boxLinkButton);
_autoDeleteSettings->setClickedCallback([=] {
getDelegate()->show(
Box(
HistoryView::Controls::AutoDeleteSettingsBox,
_wipeHistoryPeer),
Ui::LayerOption(0));
});
}
if (canDelete) {
addButton(
deleteText->value(),
[=] { deleteAndClear(); },
*deleteStyle);
addButton(tr::lng_cancel(), [=] { closeBox(); });
} else {
addButton(tr::lng_about_done(), [=] { closeBox(); });
}
auto fullHeight = st::boxPadding.top()
+ _text->height()
+ st::boxPadding.bottom();
if (_moderateFrom) {
fullHeight += st::boxMediumSkip;
if (_banUser) {
fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
}
fullHeight += _reportSpam->heightNoMargins();
if (_deleteAll) {
fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
}
} else if (_revoke) {
fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
}
if (_autoDeleteSettings) {
fullHeight += st::boxMediumSkip
+ _autoDeleteSettings->height()
+ st::boxLittleSkip;
}
setDimensions(st::boxWidth, fullHeight);
}
bool DeleteMessagesBox::hasScheduledMessages() const {
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
if (item->isScheduled()) {
return true;
}
}
}
return false;
}
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
auto result = (PeerData*)nullptr;
for (const auto &fullId : _ids) {
if (const auto item = _session->data().message(fullId)) {
const auto peer = item->history()->peer;
if (!result) {
result = peer;
} else if (result != peer) {
return nullptr;
}
}
}
return result;
}
auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
-> std::optional<RevokeConfig> {
auto result = RevokeConfig();
if (peer == _wipeHistoryPeer) {
if (!peer->canRevokeFullHistory()) {
return std::nullopt;
} else if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
user->firstName);
} else if (_wipeHistoryJustClear) {
return std::nullopt;
} else {
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
}
const auto items = ranges::views::all(
_ids
) | ranges::views::transform([&](FullMsgId id) {
return peer->owner().message(id);
}) | ranges::views::filter([](HistoryItem *item) {
return (item != nullptr);
}) | ranges::to_vector;
if (items.size() != _ids.size()) {
// We don't have information about all messages.
return std::nullopt;
}
const auto now = base::unixtime::now();
const auto canRevoke = [&](HistoryItem * item) {
return item->canDeleteForEveryone(now);
};
const auto cannotRevoke = [&](HistoryItem *item) {
return !item->canDeleteForEveryone(now);
};
const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
auto outgoing = items | ranges::views::filter(&HistoryItem::out);
const auto canRevokeOutgoingCount = canRevokeAll
? -1
: ranges::count_if(outgoing, canRevoke);
if (canRevokeAll) {
if (const auto user = peer->asUser()) {
result.checkbox = tr::lng_delete_for_other_check(
tr::now,
lt_user,
user->firstName);
} else {
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
}
return result;
} else if (canRevokeOutgoingCount > 0) {
result.checkbox = tr::lng_delete_for_other_my(tr::now);
if (const auto user = peer->asUser()) {
if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_user_one(
tr::now,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_user(
tr::now,
lt_count,
canRevokeOutgoingCount,
lt_user,
Ui::Text::Bold(user->shortName()),
Ui::Text::WithEntities);
}
} else if (canRevokeOutgoingCount == 1) {
result.description = tr::lng_selected_unsend_about_group_one(
tr::now,
Ui::Text::WithEntities);
} else {
result.description = tr::lng_selected_unsend_about_group(
tr::now,
lt_count,
canRevokeOutgoingCount,
Ui::Text::WithEntities);
}
return result;
}
return std::nullopt;
}
void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
auto top = _text->bottomNoMargins() + st::boxMediumSkip;
if (_moderateFrom) {
if (_banUser) {
_banUser->moveToLeft(st::boxPadding.left(), top);
top += _banUser->heightNoMargins() + st::boxLittleSkip;
}
_reportSpam->moveToLeft(st::boxPadding.left(), top);
top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
if (_deleteAll) {
_deleteAll->moveToLeft(st::boxPadding.left(), top);
top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
}
} else if (_revoke) {
const auto availableWidth = width() - 2 * st::boxPadding.left();
_revoke->resizeToNaturalWidth(availableWidth);
_revoke->moveToLeft(st::boxPadding.left(), top);
top += _revoke->heightNoMargins() + st::boxLittleSkip;
}
if (_autoDeleteSettings) {
top += st::boxMediumSkip - st::boxLittleSkip;
_autoDeleteSettings->moveToLeft(st::boxPadding.left(), top);
}
}
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
// Don't make the clearing history so easy.
if (!_wipeHistoryPeer) {
deleteAndClear();
}
} else {
BoxContent::keyPressEvent(e);
}
}
void DeleteMessagesBox::deleteAndClear() {
const auto revoke = _revoke ? _revoke->checked() : false;
if (const auto peer = _wipeHistoryPeer) {
const auto justClear = _wipeHistoryJustClear;
closeBox();
if (justClear) {
peer->session().api().clearHistory(peer, revoke);
} else {
for (const auto &controller : peer->session().windows()) {
if (controller->activeChatCurrent().peer() == peer) {
Ui::showChatsList(&peer->session());
}
}
// Don't delete old history by default,
// because Android app doesn't.
//
//if (const auto from = peer->migrateFrom()) {
// peer->session().api().deleteConversation(from, false);
//}
peer->session().api().deleteConversation(peer, revoke);
}
return;
}
if (_moderateFrom) {
if (_banUser && _banUser->checked()) {
_moderateInChannel->session().api().kickParticipant(
_moderateInChannel,
_moderateFrom,
ChatRestrictionsInfo());
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(
MTPchannels_ReportSpam(
_moderateInChannel->inputChannel,
_moderateFrom->inputUser,
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
).send();
}
if (_deleteAll && _deleteAll->checked()) {
_moderateInChannel->session().api().deleteAllFromUser(
_moderateInChannel,
_moderateFrom);
}
}
if (_deleteConfirmedCallback) {
_deleteConfirmedCallback();
}
// deleteMessages can initiate closing of the current section,
// which will cause this box to be destroyed.
const auto session = _session;
const auto weak = Ui::MakeWeak(this);
session->data().histories().deleteMessages(_ids, revoke);
if (const auto strong = weak.data()) {
strong->closeBox();
}
session->data().sendHistoryChangeNotifications();
}

View File

@@ -0,0 +1,74 @@
/*
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
#include "boxes/abstract_box.h"
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class Checkbox;
class FlatLabel;
class LinkButton;
} // namespace Ui
class DeleteMessagesBox final : public Ui::BoxContent {
public:
DeleteMessagesBox(
QWidget*,
not_null<HistoryItem*> item,
bool suggestModerateActions);
DeleteMessagesBox(
QWidget*,
not_null<Main::Session*> session,
MessageIdsList &&selected);
DeleteMessagesBox(QWidget*, not_null<PeerData*> peer, bool justClear);
void setDeleteConfirmedCallback(Fn<void()> callback) {
_deleteConfirmedCallback = std::move(callback);
}
protected:
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
struct RevokeConfig {
QString checkbox;
TextWithEntities description;
};
void deleteAndClear();
[[nodiscard]] PeerData *checkFromSinglePeer() const;
[[nodiscard]] bool hasScheduledMessages() const;
[[nodiscard]] std::optional<RevokeConfig> revokeText(
not_null<PeerData*> peer) const;
const not_null<Main::Session*> _session;
PeerData * const _wipeHistoryPeer = nullptr;
const bool _wipeHistoryJustClear = false;
const MessageIdsList _ids;
UserData *_moderateFrom = nullptr;
ChannelData *_moderateInChannel = nullptr;
bool _moderateBan = false;
bool _moderateDeleteAll = false;
object_ptr<Ui::FlatLabel> _text = { nullptr };
object_ptr<Ui::Checkbox> _revoke = { nullptr };
object_ptr<Ui::Checkbox> _banUser = { nullptr };
object_ptr<Ui::Checkbox> _reportSpam = { nullptr };
object_ptr<Ui::Checkbox> _deleteAll = { nullptr };
object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr };
Fn<void()> _deleteConfirmedCallback;
};

View File

@@ -39,11 +39,6 @@ using DictState = BlobState;
using QueryCallback = Fn<void(const QString &)>;
constexpr auto kMaxQueryLength = 15;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
#define OLD_QT
using QStringView = QString;
#endif
class Inner : public Ui::RpWidget {
public:
Inner(

View File

@@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "ui/text/text_options.h"
#include "storage/localstorage.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "core/application.h"
@@ -478,7 +478,7 @@ void Rows::showMenu(int index) {
}
}
const auto toggle = menuToggleArea(row);
const auto parentTopLeft = window()->mapToGlobal({ 0, 0 });
const auto parentTopLeft = window()->mapToGlobal(QPoint());
const auto buttonTopLeft = mapToGlobal(toggle.topLeft());
const auto parent = QRect(parentTopLeft, window()->size());
const auto button = QRect(buttonTopLeft, toggle.size());

View File

@@ -0,0 +1,152 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/max_invite_box.h"
#include "api/api_invite_links.h"
#include "apiwrap.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace {
TextParseOptions kInformBoxTextOptions = {
(TextParseLinks
| TextParseMultiline
| TextParseMarkdown
| TextParseRichText), // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
} // namespace
MaxInviteBox::MaxInviteBox(QWidget*, not_null<ChannelData*> channel)
: BoxContent()
, _channel(channel)
, _text(
st::boxLabelStyle,
tr::lng_participant_invite_sorry(
tr::now,
lt_count,
channel->session().serverConfig().chatSizeMax),
kInformBoxTextOptions,
(st::boxWidth
- st::boxPadding.left()
- st::defaultBox.buttonPadding.right())) {
}
void MaxInviteBox::prepare() {
setMouseTracking(true);
addButton(tr::lng_box_ok(), [=] { closeBox(); });
_textWidth = st::boxWidth
- st::boxPadding.left()
- st::defaultBox.buttonPadding.right();
_textHeight = std::min(
_text.countHeight(_textWidth),
16 * st::boxLabelStyle.lineHeight);
setDimensions(
st::boxWidth,
st::boxPadding.top()
+ _textHeight
+ st::boxTextFont->height
+ st::boxTextFont->height * 2
+ st::newGroupLinkPadding.bottom());
if (_channel->inviteLink().isEmpty()) {
_channel->session().api().requestFullPeer(_channel);
}
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());
}
void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) {
updateSelected(e->globalPos());
}
void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_linkOver) {
if (!_channel->inviteLink().isEmpty()) {
QGuiApplication::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
_creatingInviteLink = true;
_channel->session().api().inviteLinks().create(_channel);
}
}
}
void MaxInviteBox::leaveEventHook(QEvent *e) {
updateSelected(QCursor::pos());
}
void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) {
const auto p = QPoint(mapFromGlobal(cursorGlobalPosition));
const auto linkOver = _invitationLink.contains(p);
if (linkOver != _linkOver) {
_linkOver = linkOver;
update();
setCursor(_linkOver ? style::cur_pointer : style::cur_default);
}
}
void MaxInviteBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
// draw box title / text
p.setPen(st::boxTextFg);
_text.drawLeftElided(
p,
st::boxPadding.left(),
st::boxPadding.top(),
_textWidth,
width(),
16,
style::al_left);
auto option = QTextOption(style::al_left);
option.setWrapMode(QTextOption::WrapAnywhere);
p.setFont(_linkOver
? st::defaultInputField.font->underline()
: st::defaultInputField.font);
p.setPen(st::defaultLinkButton.color);
const auto inviteLinkText = _channel->inviteLink().isEmpty()
? tr::lng_group_invite_create(tr::now)
: _channel->inviteLink();
p.drawText(_invitationLink, inviteLinkText, option);
}
void MaxInviteBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_invitationLink = myrtlrect(
st::boxPadding.left(),
st::boxPadding.top() + _textHeight + st::boxTextFont->height,
width() - st::boxPadding.left() - st::boxPadding.right(),
2 * st::boxTextFont->height);
}

View File

@@ -0,0 +1,39 @@
/*
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
#include "boxes/abstract_box.h"
class MaxInviteBox final : public Ui::BoxContent {
public:
MaxInviteBox(QWidget*, not_null<ChannelData*> channel);
protected:
void prepare() override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
private:
void updateSelected(const QPoint &cursorGlobalPosition);
not_null<ChannelData*> _channel;
Ui::Text::String _text;
int32 _textWidth, _textHeight;
QRect _invitationLink;
bool _linkOver = false;
bool _creatingInviteLink = false;
QPoint _lastMousePos;
};

View File

@@ -9,8 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h"
#include "ui/boxes/confirm_box.h"
#include "base/unixtime.h"
#include "mainwindow.h"
#include "apiwrap.h"
@@ -24,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/sent_code_field.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "passport/passport_encryption.h"
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_passport.h"
#include "styles/style_boxes.h"
#include "base/qt_adapters.h"
namespace {
@@ -107,7 +108,7 @@ void StartPendingReset(
auto finish = [=](const QString &message) mutable {
if (const auto strong = weak.data()) {
if (!message.isEmpty()) {
strong->getDelegate()->show(Box<InformBox>(message));
strong->getDelegate()->show(Box<Ui::InformBox>(message));
}
strong->closeBox();
}
@@ -138,7 +139,7 @@ void StartPendingReset(
lt_count,
minutes);
if (const auto strong = weak.data()) {
strong->getDelegate()->show(Box<InformBox>(
strong->getDelegate()->show(Box<Ui::InformBox>(
tr::lng_cloud_password_reset_later(
tr::now,
lt_duration,
@@ -440,7 +441,7 @@ void PasscodeBox::recoverPasswordDone(
if (weak) {
_newPasswordSet.fire_copy(newPasswordBytes);
if (weak) {
getDelegate()->show(Box<InformBox>(
getDelegate()->show(Box<Ui::InformBox>(
tr::lng_cloud_password_updated(tr::now)));
if (weak) {
closeBox();
@@ -462,7 +463,7 @@ void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
: _oldPasscode->isHidden()
? tr::lng_cloud_password_was_set(tr::now)
: tr::lng_cloud_password_updated(tr::now);
getDelegate()->show(Box<InformBox>(text));
getDelegate()->show(Box<Ui::InformBox>(text));
if (weak) {
closeBox();
}
@@ -525,7 +526,7 @@ void PasscodeBox::setPasswordFail(
const MTP::Error &error) {
const auto prefix = qstr("EMAIL_UNCONFIRMED_");
if (error.type().startsWith(prefix)) {
const auto codeLength = error.type().midRef(prefix.size()).toInt();
const auto codeLength = base::StringViewMid(error.type(), prefix.size()).toInt();
closeReplacedBy();
_setRequest = 0;
@@ -562,7 +563,7 @@ void PasscodeBox::validateEmail(
const auto weak = Ui::MakeWeak(this);
_clearUnconfirmedPassword.fire({});
if (weak) {
auto box = Box<InformBox>(
auto box = Box<Ui::InformBox>(
Lang::Hard::EmailConfirmationExpired());
weak->getDelegate()->show(
std::move(box),
@@ -689,7 +690,7 @@ void PasscodeBox::save(bool force) {
if (!onlyCheck && !_recoverEmail->isHidden() && email.isEmpty() && !force) {
_skipEmailWarning = true;
_replacedBy = getDelegate()->show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_cloud_password_about_recover(tr::now),
tr::lng_cloud_password_skip_email(tr::now),
st::attentionBoxButton,
@@ -726,7 +727,7 @@ void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
send();
close();
};
getDelegate()->show(Box<ConfirmBox>(
getDelegate()->show(Box<Ui::ConfirmBox>(
tr::lng_cloud_password_passport_losing(tr::now),
tr::lng_continue(tr::now),
confirmed));
@@ -797,7 +798,7 @@ void PasscodeBox::requestPasswordData() {
}
void PasscodeBox::serverError() {
getDelegate()->show(Box<InformBox>(Lang::Hard::ServerError()));
getDelegate()->show(Box<Ui::InformBox>(Lang::Hard::ServerError()));
closeBox();
}
@@ -952,7 +953,7 @@ void PasscodeBox::suggestSecretReset(const QString &newPassword) {
resetSecret(check, newPassword, std::move(close));
});
};
getDelegate()->show(Box<ConfirmBox>(
getDelegate()->show(Box<Ui::ConfirmBox>(
Lang::Hard::PassportCorruptedChange(),
Lang::Hard::PassportCorruptedReset(),
std::move(resetSecretAndSave)));
@@ -1085,7 +1086,7 @@ void PasscodeBox::recoverByEmail() {
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
*confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
tr::lng_cloud_password_reset_no_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
@@ -1170,7 +1171,7 @@ RecoverBox::RecoverBox(
}
});
});
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
*confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
tr::lng_cloud_password_reset_with_email(tr::now),
tr::lng_cloud_password_reset_ok(tr::now),
reset));
@@ -1272,7 +1273,7 @@ void RecoverBox::submit() {
send();
close();
};
getDelegate()->show(Box<ConfirmBox>(
getDelegate()->show(Box<Ui::ConfirmBox>(
tr::lng_cloud_password_passport_losing(tr::now),
tr::lng_continue(tr::now),
confirmed));
@@ -1297,7 +1298,7 @@ void RecoverBox::proceedToClear() {
_submitRequest = 0;
_newPasswordSet.fire({});
getDelegate()->show(
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
Ui::LayerOption::CloseOther);
}
@@ -1345,7 +1346,7 @@ void RecoverBox::checkSubmitFail(const MTP::Error &error) {
if (err == qstr("PASSWORD_EMPTY")) {
_newPasswordSet.fire(QByteArray());
getDelegate()->show(
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
Ui::LayerOption::CloseOther);
} else if (err == qstr("PASSWORD_RECOVERY_NA")) {
closeBox();
@@ -1386,7 +1387,8 @@ RecoveryEmailValidation ConfirmRecoveryEmail(
reloads->fire({});
if (*weak) {
(*weak)->getDelegate()->show(
Box<InformBox>(tr::lng_cloud_password_was_set(tr::now)),
Box<Ui::InformBox>(
tr::lng_cloud_password_was_set(tr::now)),
Ui::LayerOption::CloseOther);
}
}).fail([=](const MTP::Error &error) {
@@ -1398,7 +1400,7 @@ RecoveryEmailValidation ConfirmRecoveryEmail(
} else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
cancels->fire({});
if (*weak) {
auto box = Box<InformBox>(
auto box = Box<Ui::InformBox>(
Lang::Hard::EmailConfirmationExpired());
(*weak)->getDelegate()->show(
std::move(box),

View File

@@ -270,6 +270,7 @@ bool PeerListController::hasComplexSearch() const {
void PeerListController::search(const QString &query) {
Expects(hasComplexSearch());
_searchController->searchQuery(query);
}
@@ -518,6 +519,63 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
}
int PeerListRow::elementsCount() const {
return 1;
}
QRect PeerListRow::elementGeometry(int element, int outerWidth) const {
if (element != 1) {
return QRect();
}
const auto size = rightActionSize();
if (size.isEmpty()) {
return QRect();
}
const auto margins = rightActionMargins();
const auto right = margins.right();
const auto top = margins.top();
const auto left = outerWidth - right - size.width();
return QRect(QPoint(left, top), size);
}
bool PeerListRow::elementDisabled(int element) const {
return (element == 1) && rightActionDisabled();
}
bool PeerListRow::elementOnlySelect(int element) const {
return false;
}
void PeerListRow::elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) {
if (element == 1) {
rightActionAddRipple(point, std::move(updateCallback));
}
}
void PeerListRow::elementsStopLastRipple() {
rightActionStopLastRipple();
}
void PeerListRow::elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) {
const auto geometry = elementGeometry(1, outerWidth);
if (!geometry.isEmpty()) {
rightActionPaint(
p,
geometry.x(),
geometry.y(),
outerWidth,
selected,
(selectedElement == 1));
}
}
QString PeerListRow::generateName() {
return peer()->name;
}
@@ -1240,7 +1298,7 @@ int PeerListContent::resizeGetHeight(int newWidth) {
return belowTop + _belowHeight;
}
void PeerListContent::enterEventHook(QEvent *e) {
void PeerListContent::enterEventHook(QEnterEvent *e) {
setMouseTracking(true);
}
@@ -1272,11 +1330,16 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
auto updateCallback = [this, row, hint = _selected.index] {
updateRow(row, hint);
};
if (_selected.action) {
auto actionRect = getActiveActionRect(row, _selected.index);
if (!actionRect.isEmpty()) {
auto point = mapFromGlobal(QCursor::pos()) - actionRect.topLeft();
row->addActionRipple(point, std::move(updateCallback));
if (_selected.element) {
const auto elementRect = getElementRect(
row,
_selected.index,
_selected.element);
if (!elementRect.isEmpty()) {
row->elementAddRipple(
_selected.element,
mapFromGlobal(QCursor::pos()) - elementRect.topLeft(),
std::move(updateCallback));
}
} else {
auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
@@ -1291,7 +1354,7 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
}
}
}
if (anim::Disabled()) {
if (anim::Disabled() && !_selected.element) {
mousePressReleased(e->button());
}
}
@@ -1308,8 +1371,8 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
setPressed(Selected());
if (button == Qt::LeftButton && pressed == _selected) {
if (auto row = getRow(pressed.index)) {
if (pressed.action) {
_controller->rowActionClicked(row);
if (pressed.element) {
_controller->rowElementClicked(row, pressed.element);
} else {
_controller->rowClicked(row);
}
@@ -1386,9 +1449,11 @@ void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
}
void PeerListContent::setPressed(Selected pressed) {
if (auto row = getRow(_pressed.index)) {
if (_pressed == pressed) {
return;
} else if (const auto row = getRow(_pressed.index)) {
row->stopLastRipple();
row->stopLastActionRipple();
row->elementsStopLastRipple();
}
_pressed = pressed;
}
@@ -1401,6 +1466,7 @@ crl::time PeerListContent::paintRow(
Assert(row != nullptr);
row->lazyInitialize(_st.item);
const auto outerWidth = width();
auto refreshStatusAt = row->refreshStatusTime();
if (refreshStatusAt > 0 && now >= refreshStatusAt) {
@@ -1417,8 +1483,8 @@ crl::time PeerListContent::paintRow(
: (_pressed.index.value >= 0)
? _pressed
: _selected;
const auto selected = (active.index == index);
const auto actionSelected = (selected && active.action);
const auto selected = (active.index == index)
&& (!active.element || !row->elementOnlySelect(active.element));
if (_mode == Mode::Custom) {
_controller->customRowPaint(p, now, row, selected);
@@ -1428,27 +1494,29 @@ crl::time PeerListContent::paintRow(
const auto &bg = selected
? _st.item.button.textBgOver
: _st.item.button.textBg;
p.fillRect(0, 0, width(), _rowHeight, bg);
row->paintRipple(p, 0, 0, width());
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
row->paintRipple(p, 0, 0, outerWidth);
row->paintUserpic(
p,
_st.item,
_st.item.photoPosition.x(),
_st.item.photoPosition.y(),
width());
outerWidth);
p.setPen(st::contactsNameFg);
auto skipRight = _st.item.photoPosition.x();
auto actionSize = row->actionSize();
auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins();
auto rightActionSize = row->rightActionSize();
auto rightActionMargins = rightActionSize.isEmpty()
? QMargins()
: row->rightActionMargins();
auto &name = row->name();
auto namex = _st.item.namePosition.x();
auto namew = width() - namex - skipRight;
if (!actionSize.isEmpty()) {
namew -= actionMargins.left()
+ actionSize.width()
+ actionMargins.right()
auto namew = outerWidth - namex - skipRight;
if (!rightActionSize.isEmpty()) {
namew -= rightActionMargins.left()
+ rightActionSize.width()
+ rightActionMargins.right()
- skipRight;
}
auto statusw = namew;
@@ -1465,20 +1533,6 @@ crl::time PeerListContent::paintRow(
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());
if (!actionSize.isEmpty()) {
auto actionLeft = width()
- actionMargins.right()
- actionSize.width();
auto actionTop = actionMargins.top();
row->paintAction(
p,
actionLeft,
actionTop,
width(),
selected,
actionSelected);
}
p.setFont(st::contactsStatusFont);
if (row->isSearchResult()
&& !_mentionHighlight.isEmpty()
@@ -1507,6 +1561,13 @@ crl::time PeerListContent::paintRow(
} else {
row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
}
row->elementsPaint(
p,
width(),
selected,
(active.index == index) ? active.element : 0);
return refreshStatusIn;
}
@@ -1572,7 +1633,7 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
}
_selected.index.value = newSelectedIndex;
_selected.action = false;
_selected.element = 0;
if (newSelectedIndex >= 0) {
auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0;
auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height();
@@ -1797,7 +1858,7 @@ void PeerListContent::setSelected(Selected selected) {
}
_selected = selected;
updateRow(_selected.index);
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
setCursor(_selected.element ? style::cur_pointer : style::cur_default);
_selectedIndex = _selected.index.value;
}
@@ -1858,25 +1919,30 @@ void PeerListContent::selectByMouse(QPoint globalPosition) {
rowsPointY - (selected.index.value * _rowHeight)))) {
selected = Selected();
} else if (!customMode) {
if (getActiveActionRect(row, selected.index).contains(point)) {
selected.action = true;
for (auto i = 0, count = row->elementsCount(); i != count; ++i) {
const auto rect = getElementRect(row, selected.index, i + 1);
if (rect.contains(point)) {
selected.element = i + 1;
break;
}
}
}
}
setSelected(selected);
}
QRect PeerListContent::getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const {
auto actionSize = row->actionSize();
if (actionSize.isEmpty() || row->actionDisabled()) {
QRect PeerListContent::getElementRect(
not_null<PeerListRow*> row,
RowIndex index,
int element) const {
if (row->elementDisabled(element)) {
return QRect();
}
auto actionMargins = row->actionMargins();
auto actionRight = actionMargins.right();
auto actionTop = actionMargins.top();
auto actionLeft = width() - actionRight - actionSize.width();
auto rowTop = getRowTop(index);
return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height());
const auto geometry = row->elementGeometry(element, width());
if (geometry.isEmpty()) {
return QRect();
}
return geometry.translated(0, getRowTop(index));
}
int PeerListContent::rowsTop() const {

View File

@@ -47,6 +47,7 @@ using PaintRoundImageCallback = Fn<void(
bool respectSavedMessagesChat);
using PeerListRowId = uint64;
class PeerListRow {
public:
enum class State {
@@ -100,20 +101,17 @@ public:
int y,
int outerWidth,
bool selected);
virtual QSize actionSize() const {
virtual QSize rightActionSize() const {
return QSize();
}
virtual bool actionDisabled() const {
return false;
}
virtual QMargins actionMargins() const {
virtual QMargins rightActionMargins() const {
return QMargins();
}
virtual void addActionRipple(QPoint point, Fn<void()> updateCallback) {
virtual bool rightActionDisabled() const {
return false;
}
virtual void stopLastActionRipple() {
}
virtual void paintAction(
virtual void rightActionPaint(
Painter &p,
int x,
int y,
@@ -121,6 +119,28 @@ public:
bool selected,
bool actionSelected) {
}
virtual void rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) {
}
virtual void rightActionStopLastRipple() {
}
// By default elements code falls back to a simple right action code.
virtual int elementsCount() const;
virtual QRect elementGeometry(int element, int outerWidth) const;
virtual bool elementDisabled(int element) const;
virtual bool elementOnlySelect(int element) const;
virtual void elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback);
virtual void elementsStopLastRipple();
virtual void elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement);
virtual void refreshName(const style::PeerListItem &st);
const Ui::Text::String &name() const {
@@ -405,11 +425,21 @@ public:
const style::PeerList &computeListSt() const;
const style::MultiSelect &computeSelectSt() const;
virtual void prepare() = 0;
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
virtual Main::Session &session() const = 0;
virtual void rowActionClicked(not_null<PeerListRow*> row) {
virtual void prepare() = 0;
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
}
// By default elements code falls back to a simple right action code.
virtual void rowElementClicked(not_null<PeerListRow*> row, int element) {
if (element == 1) {
rowRightActionClicked(row);
}
}
virtual void loadMoreRows() {
}
virtual void itemDeselectedHook(not_null<PeerData*> peer) {
@@ -622,7 +652,7 @@ protected:
int visibleBottom) override;
void paintEvent(QPaintEvent *e) override;
void enterEventHook(QEvent *e) override;
void enterEventHook(QEnterEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
@@ -653,15 +683,20 @@ private:
struct Selected {
Selected() {
}
Selected(RowIndex index, bool action) : index(index), action(action) {
Selected(RowIndex index, int element)
: index(index)
, element(element) {
}
Selected(int index, bool action) : index(index), action(action) {
Selected(int index, int element)
: index(index)
, element(element) {
}
RowIndex index;
bool action = false;
int element = 0;
};
friend inline bool operator==(Selected a, Selected b) {
return (a.index == b.index) && (a.action == b.action);
return (a.index == b.index) && (a.element == b.element);
}
friend inline bool operator!=(Selected a, Selected b) {
return !(a == b);
@@ -689,8 +724,13 @@ private:
void updateRow(RowIndex row);
int getRowTop(RowIndex row) const;
PeerListRow *getRow(RowIndex element);
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
RowIndex findRowIndex(
not_null<PeerListRow*> row,
RowIndex hint = RowIndex());
QRect getElementRect(
not_null<PeerListRow*> row,
RowIndex index,
int element) const;
bool showRowMenu(
RowIndex index,

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "base/random.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/ui_utility.h"
#include "main/main_session.h"
@@ -136,11 +136,11 @@ void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) {
refreshActionLink();
}
QSize PeerListRowWithLink::actionSize() const {
QSize PeerListRowWithLink::rightActionSize() const {
return QSize(_actionWidth, st::normalFont->height);
}
QMargins PeerListRowWithLink::actionMargins() const {
QMargins PeerListRowWithLink::rightActionMargins() const {
return QMargins(
st::contactsCheckPosition.x(),
(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2,
@@ -148,7 +148,7 @@ QMargins PeerListRowWithLink::actionMargins() const {
0);
}
void PeerListRowWithLink::paintAction(
void PeerListRowWithLink::rightActionPaint(
Painter &p,
int x,
int y,
@@ -482,7 +482,7 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
return tr::lng_bot_sure_share_game_group(tr::now, lt_group, chat->name);
}();
Ui::show(
Box<ConfirmBox>(confirmText, std::move(send)),
Box<Ui::ConfirmBox>(confirmText, std::move(send)),
Ui::LayerOption::KeepOther);
}
@@ -490,7 +490,7 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
if (const auto megagroup = chat->asMegagroup()) {
if (!megagroup->canAddMembers()) {
Ui::show(
Box<InformBox>(tr::lng_error_cant_add_member(tr::now)),
Box<Ui::InformBox>(tr::lng_error_cant_add_member(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
@@ -500,7 +500,7 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
});
auto confirmText = tr::lng_bot_sure_invite(tr::now, lt_group, chat->name);
Ui::show(
Box<ConfirmBox>(confirmText, send),
Box<Ui::ConfirmBox>(confirmText, send),
Ui::LayerOption::KeepOther);
}

View File

@@ -47,9 +47,9 @@ public:
private:
void refreshActionLink();
QSize actionSize() const override;
QMargins actionMargins() const override;
void paintAction(
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
void rightActionPaint(
Painter &p,
int x,
int y,

View File

@@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/max_invite_box.h"
#include "lang/lang_keys.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@@ -105,7 +106,8 @@ void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
} else if (count >= serverConfig.chatSizeMax
&& count < serverConfig.megagroupSizeMax) {
Ui::show(
Box<InformBox>(tr::lng_profile_add_more_after_create(tr::now)),
Box<Ui::InformBox>(
tr::lng_profile_add_more_after_create(tr::now)),
Ui::LayerOption::KeepOther);
}
}
@@ -565,20 +567,20 @@ void AddSpecialBoxController::showAdmin(
if (canBanMembers) {
if (!sure) {
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_sure_add_admin_unremove(tr::now),
showAdminSure),
Ui::LayerOption::KeepOther);
return;
}
} else {
Ui::show(Box<InformBox>(
Ui::show(Box<Ui::InformBox>(
tr::lng_error_cant_add_admin_unban(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
} else {
Ui::show(Box<InformBox>(
Ui::show(Box<Ui::InformBox>(
tr::lng_error_cant_add_admin_invite(tr::now)),
Ui::LayerOption::KeepOther);
return;
@@ -588,14 +590,14 @@ void AddSpecialBoxController::showAdmin(
if (canBanMembers) {
if (!sure) {
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_sure_add_admin_unremove(tr::now),
showAdminSure),
Ui::LayerOption::KeepOther);
return;
}
} else {
Ui::show(Box<InformBox>(
Ui::show(Box<Ui::InformBox>(
tr::lng_error_cant_add_admin_unban(tr::now)),
Ui::LayerOption::KeepOther);
return;
@@ -608,7 +610,7 @@ void AddSpecialBoxController::showAdmin(
? tr::lng_sure_add_admin_invite
: tr::lng_sure_add_admin_invite_channel)(tr::now);
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
showAdminSure),
Ui::LayerOption::KeepOther);
@@ -616,7 +618,8 @@ void AddSpecialBoxController::showAdmin(
}
} else {
Ui::show(
Box<InformBox>(tr::lng_error_cant_add_admin_invite(tr::now)),
Box<Ui::InformBox>(
tr::lng_error_cant_add_admin_invite(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
@@ -716,7 +719,7 @@ void AddSpecialBoxController::showRestricted(
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
if (!sure) {
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_sure_ban_admin(tr::now),
showRestrictedSure),
Ui::LayerOption::KeepOther);
@@ -724,7 +727,7 @@ void AddSpecialBoxController::showRestricted(
}
} else {
Ui::show(
Box<InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
Box<Ui::InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
@@ -813,7 +816,7 @@ void AddSpecialBoxController::kickUser(
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
if (!sure) {
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_sure_ban_admin(tr::now),
kickUserSure),
Ui::LayerOption::KeepOther);
@@ -821,7 +824,7 @@ void AddSpecialBoxController::kickUser(
}
} else {
Ui::show(
Box<InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
Box<Ui::InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
Ui::LayerOption::KeepOther);
return;
}
@@ -836,7 +839,7 @@ void AddSpecialBoxController::kickUser(
lt_user,
participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(text, kickUserSure),
Box<Ui::ConfirmBox>(text, kickUserSure),
Ui::LayerOption::KeepOther);
return;
}

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_session.h"
#include "boxes/peers/edit_peer_common.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/input_fields.h"
@@ -26,8 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kMaxUserFirstLastName = 64; // See also add_contact_box.
QString UserPhone(not_null<UserData*> user) {
const auto phone = user->phone();
return phone.isEmpty()
@@ -222,8 +221,8 @@ void Controller::initNameFields(
};
QObject::connect(first, &Ui::InputField::submitted, submit);
QObject::connect(last, &Ui::InputField::submitted, submit);
first->setMaxLength(kMaxUserFirstLastName);
first->setMaxLength(kMaxUserFirstLastName);
first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
}
void Controller::setupWarning() {

View File

@@ -10,12 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_changes.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "boxes/peer_list_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/add_contact_box.h"
#include "apiwrap.h"
#include "facades.h"
@@ -63,12 +64,12 @@ Controller::Controller(
, _chat(chat)
, _chats(std::move(chats))
, _callback(std::move(callback)) {
base::ObservableViewer(
channel->session().api().fullPeerUpdated()
) | rpl::start_with_next([=](PeerData *peer) {
if (peer == _waitForFull) {
choose(std::exchange(_waitForFull, nullptr));
}
channel->session().changes().peerUpdates(
Data::PeerUpdate::Flag::FullInfo
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer == _waitForFull);
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
choose(std::exchange(_waitForFull, nullptr));
}, lifetime());
}
@@ -152,7 +153,7 @@ void Controller::choose(not_null<ChannelData*> chat) {
onstack(chat);
};
Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
tr::lng_manage_discussion_group_link(tr::now),
sure),
@@ -184,7 +185,7 @@ void Controller::choose(not_null<ChatData*> chat) {
chat->session().api().migrateChat(chat, crl::guard(this, done));
};
Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
tr::lng_manage_discussion_group_link(tr::now),
sure),

View File

@@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/special_buttons.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "settings/settings_privacy_security.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/passcode_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_info_box.h"
@@ -34,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "core/core_cloud_password.h"
#include "base/unixtime.h"
#include "base/qt_adapters.h"
#include "apiwrap.h"
#include "api/api_cloud_password.h"
#include "main/main_session.h"
@@ -453,7 +452,14 @@ void EditAdminBox::transferOwnership() {
)).fail([=](const MTP::Error &error) {
_checkTransferRequestId = 0;
if (!handleTransferPasswordError(error)) {
getDelegate()->show(Box<ConfirmBox>(
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
const auto callback = crl::guard(this, [=] {
transferOwnershipChecked();
if (*box) {
(*box)->closeBox();
}
});
*box = getDelegate()->show(Box<Ui::ConfirmBox>(
tr::lng_rights_transfer_about(
tr::now,
lt_group,
@@ -462,7 +468,7 @@ void EditAdminBox::transferOwnership() {
Ui::Text::Bold(user()->shortName()),
Ui::Text::RichLangValue),
tr::lng_rights_transfer_sure(tr::now),
crl::guard(this, [=] { transferOwnershipChecked(); })));
callback));
}
}).send();
}
@@ -570,7 +576,7 @@ void EditAdminBox::sendTransferRequestFrom(
|| (type == qstr("SESSION_TOO_FRESH_XXX"));
}();
const auto weak = Ui::MakeWeak(this);
getDelegate()->show(Box<InformBox>(problem));
getDelegate()->show(Box<Ui::InformBox>(problem));
if (box) {
box->closeBox();
}
@@ -703,7 +709,7 @@ void EditRestrictedBox::showRestrictUntil() {
highlighted,
[this](const QDate &date) {
setRestrictUntil(
static_cast<int>(base::QDateToDateTime(date).toTime_t()));
static_cast<int>(date.startOfDay().toSecsSinceEpoch()));
}),
Ui::LayerOption::KeepOther);
_restrictUntilBox->setMaxDate(

View File

@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/add_participants_box.h"
#include "boxes/confirm_box.h"
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
#include "ui/boxes/confirm_box.h"
#include "boxes/max_invite_box.h"
#include "boxes/add_contact_box.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
@@ -924,9 +926,8 @@ void ParticipantsBoxController::Start(
});
}
};
Ui::show(
Box<PeerListBox>(std::move(controller), initBox),
Ui::LayerOption::KeepOther);
navigation->parentController()->show(
Box<PeerListBox>(std::move(controller), initBox));
}
void ParticipantsBoxController::addNewItem() {
@@ -1434,11 +1435,17 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
showRestricted(user);
} else {
Assert(_navigation != nullptr);
_navigation->showPeerInfo(participant);
if (_role != Role::Profile) {
_navigation->parentController()->show(PrepareShortInfoBox(
participant,
_navigation));
} else {
_navigation->showPeerInfo(participant);
}
}
}
void ParticipantsBoxController::rowActionClicked(
void ParticipantsBoxController::rowRightActionClicked(
not_null<PeerListRow*> row) {
const auto participant = row->peer();
const auto user = participant->asUser();
@@ -1690,7 +1697,7 @@ void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant)
lt_user,
user ? user->firstName : participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
tr::lng_box_remove(tr::now),
crl::guard(this, [=] { kickParticipantSure(participant); })),
@@ -1729,7 +1736,7 @@ void ParticipantsBoxController::kickParticipantSure(
void ParticipantsBoxController::removeAdmin(not_null<UserData*> user) {
_editBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_profile_sure_remove_admin(
tr::now,
lt_user,

View File

@@ -157,7 +157,7 @@ public:
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;

View File

@@ -0,0 +1,18 @@
/*
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 Ui::EditPeer {
constexpr auto kMaxGroupChannelTitle = 128;
constexpr auto kMaxUserFirstLastName = 64;
constexpr auto kMaxChannelDescription = 255;
constexpr auto kMinUsernameLength = 5;
constexpr auto kUsernameCheckTimeout = crl::time(200);
} // namespace Ui::EditPeer

View File

@@ -8,16 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_info_box.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "main/main_session.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/edit_peer_history_visibility_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_invite_links.h"
#include "boxes/peers/edit_linked_chat_box.h"
#include "boxes/peers/edit_peer_requests_box.h"
#include "boxes/stickers_box.h"
#include "ui/boxes/single_choice_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
@@ -249,9 +252,6 @@ void ShowEditPermissions(
namespace {
constexpr auto kMaxGroupChannelTitle = 128; // See also add_contact_box.
constexpr auto kMaxChannelDescription = 255; // See also add_contact_box.
class Controller : public base::has_weak_ptr {
public:
Controller(
@@ -298,6 +298,7 @@ private:
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
void fillPendingRequestsButton();
void submitTitle();
void submitDescription();
@@ -475,7 +476,7 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
: tr::lng_dlg_new_channel_name)(),
_peer->name),
st::editPeerTitleMargins);
result->entity()->setMaxLength(kMaxGroupChannelTitle);
result->entity()->setMaxLength(Ui::EditPeer::kMaxGroupChannelTitle);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
@@ -509,7 +510,7 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
tr::lng_create_group_description(),
_peer->about()),
st::editPeerDescriptionMargins);
result->entity()->setMaxLength(kMaxChannelDescription);
result->entity()->setMaxLength(Ui::EditPeer::kMaxChannelDescription);
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
result->entity()->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
@@ -846,6 +847,8 @@ void Controller::fillHistoryVisibilityButton() {
void Controller::fillManageSection() {
Expects(_controls.buttonsLayout != nullptr);
using namespace rpl::mappers;
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
const auto isChannel = (!chat);
@@ -1043,6 +1046,9 @@ void Controller::fillManageSection() {
},
st::infoIconMembers);
}
fillPendingRequestsButton();
if (canViewKicked) {
AddButtonWithCount(
_controls.buttonsLayout,
@@ -1090,6 +1096,33 @@ void Controller::fillManageSection() {
}
}
void Controller::fillPendingRequestsButton() {
auto pendingRequestsCount = Info::Profile::MigratedOrMeValue(
_peer
) | rpl::map(
Info::Profile::PendingRequestsCountValue
) | rpl::flatten_latest(
) | rpl::start_spawning(_controls.buttonsLayout->lifetime());
const auto wrap = _controls.buttonsLayout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_controls.buttonsLayout,
object_ptr<Ui::VerticalLayout>(
_controls.buttonsLayout)));
AddButtonWithCount(
wrap->entity(),
(_isGroup
? tr::lng_manage_peer_requests()
: tr::lng_manage_peer_requests_channel()),
rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(),
[=] { RequestsBoxController::Start(_navigation, _peer); },
st::infoIconRequests);
std::move(
pendingRequestsCount
) | rpl::start_with_next([=](int count) {
wrap->toggle(count > 0, anim::type::instant);
}, wrap->lifetime());
}
void Controller::submitTitle() {
Expects(_controls.title != nullptr);
@@ -1471,7 +1504,7 @@ void Controller::savePhoto() {
? _controls.photo->takeResultImage()
: QImage();
if (!image.isNull()) {
_peer->session().api().uploadPeerPhoto(_peer, std::move(image));
_peer->session().api().peerPhoto().upload(_peer, std::move(image));
}
_box->closeBox();
}
@@ -1487,7 +1520,7 @@ void Controller::deleteWithConfirmation() {
deleteChannel();
});
_navigation->parentController()->show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
text,
tr::lng_box_delete(tr::now),
st::attentionBoxButton,
@@ -1514,7 +1547,7 @@ void Controller::deleteChannel() {
session->api().applyUpdates(result);
//}).fail([=](const MTP::Error &error) {
// if (error.type() == qstr("CHANNEL_TOO_LARGE")) {
// Ui::show(Box<InformBox>(tr::lng_cant_delete_channel(tr::now)));
// Ui::show(Box<Ui::InformBox>(tr::lng_cant_delete_channel(tr::now)));
// }
}).send();
}

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_histories.h"
@@ -24,13 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/popup_menu.h"
#include "ui/abstract_button.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/text/text_utilities.h"
#include "ui/boxes/edit_invite_link.h"
#include "boxes/share_box.h"
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
#include "history/history_message.h" // GetErrorTextForSending.
#include "history/history.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_box.h"
#include "mainwindow.h"
#include "facades.h" // Ui::showPerProfile.
@@ -57,30 +59,110 @@ constexpr auto kShareQrPadding = 16;
using LinkData = Api::InviteLink;
class Controller final : public PeerListController {
class RequestedRow final : public PeerListRow {
public:
RequestedRow(not_null<PeerData*> peer, TimeId date);
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
void rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
};
RequestedRow::RequestedRow(not_null<PeerData*> peer, TimeId date)
: PeerListRow(peer) {
setCustomStatus(PrepareRequestedRowStatus(date));
}
QSize RequestedRow::rightActionSize() const {
return QSize(
st::inviteLinkThreeDotsIcon.width(),
st::inviteLinkThreeDotsIcon.height());
}
QMargins RequestedRow::rightActionMargins() const {
return QMargins(
0,
(st::peerListBoxItem.height - rightActionSize().height()) / 2,
st::inviteLinkThreeDotsSkip,
0);
}
void RequestedRow::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
(actionSelected
? st::inviteLinkThreeDotsIconOver
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
}
class Controller final
: public PeerListController
, public base::has_weak_ptr {
public:
enum class Role {
Requested,
Joined,
};
Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
rpl::producer<LinkData> data);
rpl::producer<LinkData> data,
Role role);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
Main::Session &session() const override;
rpl::producer<int> boxHeightValue() const override;
int descriptionTopSkipMin() const override;
struct Processed {
not_null<UserData*> user;
bool approved = false;
};
[[nodiscard]] rpl::producer<Processed> processed() const {
return _processed.events();
}
private:
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
void setupAboveJoinedWidget();
void appendSlice(const Api::JoinedByLinkSlice &slice);
void addHeaderBlock(not_null<Ui::VerticalLayout*> container);
not_null<Ui::SlideWrap<>*> addRequestedListBlock(
not_null<Ui::VerticalLayout*> container);
void updateWithProcessed(Processed processed);
[[nodiscard]] rpl::producer<LinkData> dataValue() const;
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row);
void processRequest(not_null<UserData*> user, bool approved);
const not_null<PeerData*> _peer;
const Role _role = Role::Joined;
rpl::variable<LinkData> _data;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<Processed> _processed;
QString _link;
bool _revoked = false;
@@ -220,10 +302,12 @@ void QrBox(
Controller::Controller(
not_null<PeerData*> peer,
not_null<UserData*> admin,
rpl::producer<LinkData> data)
rpl::producer<LinkData> data,
Role role)
: _peer(peer)
, _role(role)
, _data(LinkData{ .admin = admin })
, _api(&_peer->session().api().instance()) {
, _api(&session().api().instance()) {
_data = std::move(data);
const auto current = _data.current();
_link = current.link;
@@ -386,7 +470,87 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
}, lifetime());
}
not_null<Ui::SlideWrap<>*> Controller::addRequestedListBlock(
not_null<Ui::VerticalLayout*> container) {
using namespace Settings;
auto result = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(
container)));
const auto wrap = result->entity();
// Make this container occupy full width.
wrap->add(object_ptr<Ui::RpWidget>(wrap));
AddDivider(wrap);
AddSkip(wrap);
auto requestedCount = dataValue(
) | rpl::filter([](const LinkData &data) {
return data.requested > 0;
}) | rpl::map([=](const LinkData &data) {
return float64(data.requested);
});
AddSubsectionTitle(
wrap,
tr::lng_group_invite_requested_full(
lt_count_decimal,
std::move(requestedCount)));
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<
Controller
>(_peer, _data.current().admin, _data.value(), Role::Requested);
const auto content = container->add(object_ptr<PeerListContent>(
container,
controller));
delegate->setContent(content);
controller->setDelegate(delegate);
controller->processed(
) | rpl::start_with_next([=](Processed processed) {
updateWithProcessed(processed);
}, lifetime());
return result;
}
void Controller::prepare() {
if (_role == Role::Joined) {
setupAboveJoinedWidget();
_allLoaded = (_data.current().usage == 0);
const auto &inviteLinks = session().api().inviteLinks();
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
if (slice) {
appendSlice(*slice);
}
} else {
_allLoaded = (_data.current().requested == 0);
}
loadMoreRows();
}
void Controller::updateWithProcessed(Processed processed) {
const auto user = processed.user;
auto updated = _data.current();
if (processed.approved) {
++updated.usage;
if (!delegate()->peerListFindRow(user->id.value)) {
delegate()->peerListPrependRow(
std::make_unique<PeerListRow>(user));
delegate()->peerListRefreshRows();
}
}
if (updated.requested > 0) {
--updated.requested;
}
session().api().inviteLinks().applyExternalUpdate(_peer, updated);
}
void Controller::setupAboveJoinedWidget() {
using namespace Settings;
auto header = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
@@ -406,6 +570,8 @@ void Controller::prepare() {
rpl::single(langDateTime(base::unixtime::parse(current.date))));
AddSkip(container, st::membersMarginBottom);
auto requestedWrap = addRequestedListBlock(container);
const auto listHeaderWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
@@ -484,6 +650,8 @@ void Controller::prepare() {
} else {
remaining->show();
}
requestedWrap->toggle(data.requested > 0, anim::type::instant);
}, remaining->lifetime());
rpl::combine(
@@ -500,23 +668,19 @@ void Controller::prepare() {
_headerWidget = header.data();
delegate()->peerListSetAboveWidget(std::move(header));
_allLoaded = (current.usage == 0);
const auto &inviteLinks = _peer->session().api().inviteLinks();
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
if (slice) {
appendSlice(*slice);
}
loadMoreRows();
}
void Controller::loadMoreRows() {
if (_requestId || _allLoaded) {
return;
}
using Flag = MTPmessages_GetChatInviteImporters::Flag;
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
MTP_flags(Flag::f_link
| (_role == Role::Requested ? Flag::f_requested : Flag(0))),
_peer->input,
MTP_string(_link),
MTPstring(), // q
MTP_int(_lastUser ? _lastUser->date : 0),
_lastUser ? _lastUser->user->inputUser : MTP_inputUserEmpty(),
MTP_int(_lastUser ? kPerPage : kFirstPage)
@@ -534,8 +698,9 @@ void Controller::loadMoreRows() {
void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
for (const auto &user : slice.users) {
_lastUser = user;
delegate()->peerListAppendRow(
std::make_unique<PeerListRow>(user.user));
delegate()->peerListAppendRow((_role == Role::Requested)
? std::make_unique<RequestedRow>(user.user, user.date)
: std::make_unique<PeerListRow>(user.user));
}
delegate()->peerListRefreshRows();
if (delegate()->peerListFullRowsCount() > 0) {
@@ -550,6 +715,82 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::showPeerProfile(row->peer());
}
void Controller::rowRightActionClicked(not_null<PeerListRow*> row) {
if (_role != Role::Requested) {
return;
}
delegate()->peerListShowRowMenu(row, true);
}
base::unique_qptr<Ui::PopupMenu> Controller::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
auto result = createRowContextMenu(parent, row);
if (result) {
// First clear _menu value, so that we don't check row positions yet.
base::take(_menu);
// Here unique_qptr is used like a shared pointer, where
// not the last destroyed pointer destroys the object, but the first.
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
}
return result;
}
base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto user = row->peer()->asUser();
Assert(user != nullptr);
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
const auto add = _peer->isBroadcast()
? tr::lng_group_requests_add_channel(tr::now)
: tr::lng_group_requests_add(tr::now);
result->addAction(add, [=] {
processRequest(user, true);
});
result->addAction(tr::lng_group_requests_dismiss(tr::now), [=] {
processRequest(user, false);
});
return result;
}
void Controller::processRequest(
not_null<UserData*> user,
bool approved) {
const auto done = crl::guard(this, [=] {
_processed.fire({ user, approved });
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
delegate()->peerListRemoveRow(row);
delegate()->peerListRefreshRows();
}
if (approved) {
Ui::ShowMultilineToast({
.text = (_peer->isBroadcast()
? tr::lng_group_requests_was_added_channel
: tr::lng_group_requests_was_added)(
tr::now,
lt_user,
Ui::Text::Bold(user->name),
Ui::Text::WithEntities)
});
}
});
const auto fail = crl::guard(this, [=] {
_processed.fire({ user, false });
});
session().api().inviteLinks().processRequest(
_peer,
_data.current().link,
user,
approved,
done,
fail);
}
Main::Session &Controller::session() const {
return _peer->session();
}
@@ -644,6 +885,9 @@ void AddPermanentLinkBlock(
const auto value = container->lifetime().make_state<
rpl::variable<LinkData>
>();
const auto currentLinkFields = container->lifetime().make_state<
Api::InviteLink
>(Api::InviteLink{ .admin = admin });
if (admin->isSelf()) {
*value = peer->session().changes().peerFlagsValue(
peer,
@@ -652,11 +896,19 @@ void AddPermanentLinkBlock(
const auto &links = peer->session().api().inviteLinks().myLinks(
peer).links;
const auto link = links.empty() ? nullptr : &links.front();
return (link && link->permanent && !link->revoked)
? LinkData{ link->link, link->usage }
: LinkData();
if (link && link->permanent && !link->revoked) {
*currentLinkFields = *link;
return LinkData{ link->link, link->usage };
}
return LinkData();
});
} else {
rpl::duplicate(
fromList
) | rpl::start_with_next([=](const Api::InviteLink &link) {
*currentLinkFields = link;
}, container->lifetime());
*value = std::move(
fromList
) | rpl::map([](const Api::InviteLink &link) {
@@ -680,7 +932,7 @@ void AddPermanentLinkBlock(
}
});
const auto revokeLink = crl::guard(weak, [=] {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
const auto done = crl::guard(weak, [=] {
const auto close = [=] {
if (*box) {
@@ -694,7 +946,9 @@ void AddPermanentLinkBlock(
close);
});
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_about_new(tr::now), done),
Box<Ui::ConfirmBox>(
tr::lng_group_invite_about_new(tr::now),
done),
Ui::LayerOption::KeepOther);
});
@@ -813,6 +1067,9 @@ void AddPermanentLinkBlock(
state->content.value(),
st::inviteLinkJoinedRowPadding
)->setClickedCallback([=] {
if (!currentLinkFields->link.isEmpty()) {
ShowInviteLinkBox(peer, *currentLinkFields);
}
});
container->add(object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>(
@@ -868,7 +1125,7 @@ void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
}
text.append(error.first);
Ui::show(
Box<InformBox>(text),
Box<Ui::InformBox>(text),
Ui::LayerOption::KeepOther);
return;
}
@@ -938,27 +1195,37 @@ void EditLink(
peer->session().api().inviteLinks().create(
peer,
finish,
result.label,
result.expireDate,
result.usageLimit);
result.usageLimit,
result.requestApproval);
} else {
peer->session().api().inviteLinks().edit(
peer,
data.admin,
result.link,
result.label,
result.expireDate,
result.usageLimit,
result.requestApproval,
finish);
}
};
const auto isGroup = !peer->isBroadcast();
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
*box = Ui::show(
(creating
? Box(Ui::CreateInviteLinkBox, done)
? Box(Ui::CreateInviteLinkBox, isGroup, isPublic, done)
: Box(
Ui::EditInviteLinkBox,
Fields{
.link = data.link,
.label = data.label,
.expireDate = data.expireDate,
.usageLimit = data.usageLimit
.usageLimit = data.usageLimit,
.requestApproval = data.requestApproval,
.isGroup = isGroup,
.isPublic = isPublic,
},
done)),
Ui::LayerOption::KeepOther);
@@ -968,7 +1235,7 @@ void RevokeLink(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
const auto revoke = [=] {
const auto done = [=](const LinkData &data) {
if (*box) {
@@ -978,7 +1245,7 @@ void RevokeLink(
peer->session().api().inviteLinks().revoke(peer, admin, link, done);
};
*box = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
tr::lng_group_invite_revoke_about(tr::now),
revoke),
Ui::LayerOption::KeepOther);
@@ -988,7 +1255,7 @@ void DeleteLink(
not_null<PeerData*> peer,
not_null<UserData*> admin,
const QString &link) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
@@ -1002,7 +1269,7 @@ void DeleteLink(
finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
Box<Ui::ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
Ui::LayerOption::KeepOther);
}
@@ -1033,7 +1300,9 @@ void ShowInviteLinkBox(
return;
}
const auto now = base::unixtime::now();
box->setTitle(link.revoked
box->setTitle(!link.label.isEmpty()
? rpl::single(link.label)
: link.revoked
? tr::lng_manage_peer_link_invite()
: IsExpiredLink(link, now)
? tr::lng_manage_peer_link_expired()
@@ -1046,7 +1315,33 @@ void ShowInviteLinkBox(
};
Ui::show(
Box<PeerListBox>(
std::make_unique<Controller>(peer, link.admin, std::move(data)),
std::make_unique<Controller>(
peer,
link.admin,
std::move(data),
Controller::Role::Joined),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}
QString PrepareRequestedRowStatus(TimeId date) {
const auto now = QDateTime::currentDateTime();
const auto parsed = base::unixtime::parse(date);
const auto parsedDate = parsed.date();
const auto time = parsed.time().toString(cTimeFormat());
const auto generic = [&] {
return tr::lng_group_requests_status_date_time(
tr::now,
lt_date,
langDayOfMonth(parsedDate),
lt_time,
time);
};
return (parsedDate.addDays(1) < now.date())
? generic()
: (parsedDate.addDays(1) == now.date())
? tr::lng_group_requests_status_yesterday(tr::now, lt_time, time)
: (now.date() == parsedDate)
? tr::lng_group_requests_status_today(tr::now, lt_time, time)
: generic();
}

View File

@@ -50,3 +50,5 @@ void DeleteLink(
void ShowInviteLinkBox(
not_null<PeerData*> peer,
const Api::InviteLink &link);
[[nodiscard]] QString PrepareRequestedRowStatus(TimeId date);

View File

@@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_peer_invite_link.h"
#include "settings/settings_common.h" // AddDivider.
@@ -91,9 +91,9 @@ public:
QString generateShortName() override;
PaintRoundImageCallback generatePaintUserpicCallback() override;
QSize actionSize() const override;
QMargins actionMargins() const override;
void paintAction(
QSize rightActionSize() const override;
QMargins rightActionMargins() const override;
void rightActionPaint(
Painter &p,
int x,
int y,
@@ -178,6 +178,16 @@ private:
tr::now,
lt_count_decimal,
link.usageLimit - link.usage);
} else if (link.usage > 0 && link.requested > 0) {
result += ", " + tr::lng_group_invite_requested(
tr::now,
lt_count_decimal,
link.requested);
} else if (link.requested > 0) {
result = tr::lng_group_invite_requested_full(
tr::now,
lt_count_decimal,
link.requested);
}
if (link.expireDate > now) {
const auto left = (link.expireDate - now);
@@ -197,7 +207,7 @@ private:
void DeleteAllRevoked(
not_null<PeerData*> peer,
not_null<UserData*> admin) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
@@ -210,7 +220,9 @@ void DeleteAllRevoked(
finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_all_sure(tr::now), sure),
Box<Ui::ConfirmBox>(
tr::lng_group_invite_delete_all_sure(tr::now),
sure),
Ui::LayerOption::KeepOther);
}
@@ -239,8 +251,10 @@ not_null<Ui::SettingsButton*> AddCreateLinkButton(
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgActive);
const auto rect = icon->rect();
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(rect);
{
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(rect);
}
st::inviteLinkCreateIcon.paintInCenter(p, rect);
}, icon->lifetime());
return result;
@@ -263,12 +277,14 @@ void Row::update(const InviteLinkData &data, TimeId now) {
_progressTillExpire = ComputeProgress(data, now);
_color = ComputeColor(data, _progressTillExpire);
setCustomStatus(ComputeStatus(data, now));
refreshName(st::inviteLinkList.item);
_delegate->rowUpdateRow(this);
}
void Row::updateExpireProgress(TimeId now) {
const auto updated = ComputeProgress(_data, now);
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
if (base::SafeRound(_progressTillExpire * 360)
!= base::SafeRound(updated * 360)) {
_progressTillExpire = updated;
const auto color = ComputeColor(_data, _progressTillExpire);
if (_color != color) {
@@ -291,10 +307,14 @@ crl::time Row::updateExpireIn() const {
if (_data.expireDate <= start) {
return 0;
}
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
return base::SafeRound(
(_data.expireDate - start) * crl::time(1000) / 720.);
}
QString Row::generateName() {
if (!_data.label.isEmpty()) {
return _data.label;
}
auto result = _data.link;
return result.replace(
qstr("https://"),
@@ -323,21 +343,21 @@ PaintRoundImageCallback Row::generatePaintUserpicCallback() {
};
}
QSize Row::actionSize() const {
QSize Row::rightActionSize() const {
return QSize(
st::inviteLinkThreeDotsIcon.width(),
st::inviteLinkThreeDotsIcon.height());
}
QMargins Row::actionMargins() const {
QMargins Row::rightActionMargins() const {
return QMargins(
0,
(st::inviteLinkList.item.height - actionSize().height()) / 2,
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
st::inviteLinkThreeDotsSkip,
0);
}
void Row::paintAction(
void Row::rightActionPaint(
Painter &p,
int x,
int y,
@@ -367,7 +387,7 @@ public:
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
@@ -531,7 +551,7 @@ void LinksController::rowClicked(not_null<PeerListRow*> row) {
ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data());
}
void LinksController::rowActionClicked(not_null<PeerListRow*> row) {
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, true);
}
@@ -683,12 +703,14 @@ void LinksController::rowPaintIcon(
auto p = QPainter(&icon);
p.setPen(Qt::NoPen);
p.setBrush(*bg);
auto hq = PainterHighQualityEnabler(p);
auto rect = QRect(0, 0, inner, inner);
if (color == Color::Expiring || color == Color::ExpireSoon) {
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
{
auto hq = PainterHighQualityEnabler(p);
auto rect = QRect(0, 0, inner, inner);
if (color == Color::Expiring || color == Color::ExpireSoon) {
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
}
p.drawEllipse(rect);
}
p.drawEllipse(rect);
(color == Color::Revoked
? st::inviteLinkRevokedIcon
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });

View File

@@ -0,0 +1,680 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_requests_box.h"
#include "ui/effects/ripple_animation.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "base/unixtime.h"
#include "main/main_session.h"
#include "mtproto/sender.h"
#include "ui/text/text_utilities.h"
#include "ui/toasts/common_toasts.h"
#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "styles/style_boxes.h"
namespace {
constexpr auto kFirstPageCount = 16;
constexpr auto kPerPage = 200;
constexpr auto kServerSearchDelay = crl::time(1000);
constexpr auto kAcceptButton = 1;
constexpr auto kRejectButton = 2;
class RowDelegate {
public:
[[nodiscard]] virtual QSize rowAcceptButtonSize() = 0;
[[nodiscard]] virtual QSize rowRejectButtonSize() = 0;
virtual void rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) = 0;
virtual void rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) = 0;
};
class Row final : public PeerListRow {
public:
Row(
not_null<RowDelegate*> delegate,
not_null<UserData*> user,
TimeId date);
int elementsCount() const override;
QRect elementGeometry(int element, int outerWidth) const override;
bool elementDisabled(int element) const override;
bool elementOnlySelect(int element) const override;
void elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) override;
void elementsStopLastRipple() override;
void elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) override;
private:
const not_null<RowDelegate*> _delegate;
std::unique_ptr<Ui::RippleAnimation> _acceptRipple;
std::unique_ptr<Ui::RippleAnimation> _rejectRipple;
};
Row::Row(
not_null<RowDelegate*> delegate,
not_null<UserData*> user,
TimeId date)
: PeerListRow(user)
, _delegate(delegate) {
setCustomStatus(PrepareRequestedRowStatus(date));
}
int Row::elementsCount() const {
return 2;
}
QRect Row::elementGeometry(int element, int outerWidth) const {
switch (element) {
case kAcceptButton: {
const auto size = _delegate->rowAcceptButtonSize();
return QRect(st::requestAcceptPosition, size);
} break;
case kRejectButton: {
const auto accept = _delegate->rowAcceptButtonSize();
const auto size = _delegate->rowRejectButtonSize();
return QRect(
(st::requestAcceptPosition
+ QPoint(accept.width() + st::requestButtonsSkip, 0)),
size);
} break;
}
return QRect();
}
bool Row::elementDisabled(int element) const {
return false;
}
bool Row::elementOnlySelect(int element) const {
return true;
}
void Row::elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) {
const auto pointer = (element == kAcceptButton)
? &_acceptRipple
: (element == kRejectButton)
? &_rejectRipple
: nullptr;
if (!pointer) {
return;
}
auto &ripple = *pointer;
if (!ripple) {
auto mask = Ui::RippleAnimation::roundRectMask(
(element == kAcceptButton
? _delegate->rowAcceptButtonSize()
: _delegate->rowRejectButtonSize()),
st::buttonRadius);
ripple = std::make_unique<Ui::RippleAnimation>(
(element == kAcceptButton
? st::requestsAcceptButton.ripple
: st::requestsRejectButton.ripple),
std::move(mask),
std::move(updateCallback));
}
ripple->add(point);
}
void Row::elementsStopLastRipple() {
if (_acceptRipple) {
_acceptRipple->lastStop();
}
if (_rejectRipple) {
_rejectRipple->lastStop();
}
}
void Row::elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) {
const auto accept = elementGeometry(kAcceptButton, outerWidth);
const auto reject = elementGeometry(kRejectButton, outerWidth);
const auto over = [&](int element) {
return (selectedElement == element);
};
_delegate->rowPaintAccept(
p,
accept,
_acceptRipple,
outerWidth,
over(kAcceptButton));
_delegate->rowPaintReject(
p,
reject,
_rejectRipple,
outerWidth,
over(kRejectButton));
}
} // namespace
class RequestsBoxController::RowHelper final : public RowDelegate {
public:
explicit RowHelper(bool isGroup);
[[nodiscard]] QSize rowAcceptButtonSize() override;
[[nodiscard]] QSize rowRejectButtonSize() override;
void rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) override;
void rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) override;
private:
void paintButton(
Painter &p,
QRect geometry,
const style::RoundButton &st,
const Ui::RoundRect &rect,
const Ui::RoundRect &rectOver,
std::unique_ptr<Ui::RippleAnimation> &ripple,
const QString &text,
int textWidth,
int outerWidth,
bool over);
Ui::RoundRect _acceptRect;
Ui::RoundRect _acceptRectOver;
Ui::RoundRect _rejectRect;
Ui::RoundRect _rejectRectOver;
QString _acceptText;
QString _rejectText;
int _acceptTextWidth = 0;
int _rejectTextWidth = 0;
};
RequestsBoxController::RowHelper::RowHelper(bool isGroup)
: _acceptRect(st::buttonRadius, st::requestsAcceptButton.textBg)
, _acceptRectOver(st::buttonRadius, st::requestsAcceptButton.textBgOver)
, _rejectRect(st::buttonRadius, st::requestsRejectButton.textBg)
, _rejectRectOver(st::buttonRadius, st::requestsRejectButton.textBgOver)
, _acceptText(isGroup
? tr::lng_group_requests_add(tr::now)
: tr::lng_group_requests_add_channel(tr::now))
, _rejectText(tr::lng_group_requests_dismiss(tr::now))
, _acceptTextWidth(st::requestsAcceptButton.font->width(_acceptText))
, _rejectTextWidth(st::requestsRejectButton.font->width(_rejectText)) {
}
RequestsBoxController::RequestsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer)
: PeerListController(CreateSearchController(peer))
, _navigation(navigation)
, _helper(std::make_unique<RowHelper>(!peer->isBroadcast()))
, _peer(peer)
, _api(&_peer->session().mtp()) {
setStyleOverrides(&st::requestsBoxList);
subscribeToMigration();
}
RequestsBoxController::~RequestsBoxController() = default;
void RequestsBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
auto controller = std::make_unique<RequestsBoxController>(
navigation,
peer->migrateToOrMe());
const auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
};
navigation->parentController()->show(
Box<PeerListBox>(std::move(controller), initBox));
}
Main::Session &RequestsBoxController::session() const {
return _peer->session();
}
auto RequestsBoxController::CreateSearchController(not_null<PeerData*> peer)
-> std::unique_ptr<PeerListSearchController> {
return std::make_unique<RequestsBoxSearchController>(peer);
}
std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return createRow(user);
}
return nullptr;
}
void RequestsBoxController::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(_peer->isBroadcast()
? tr::lng_manage_peer_requests_channel()
: tr::lng_manage_peer_requests());
setDescriptionText(tr::lng_contacts_loading(tr::now));
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
loadMoreRows();
}
void RequestsBoxController::loadMoreRows() {
if (searchController() && searchController()->loadMoreRows()) {
return;
} else if (_loadRequestId || _allLoaded) {
return;
}
// First query is small and fast, next loads a lot of rows.
const auto limit = _offsetDate ? kPerPage : kFirstPageCount;
using Flag = MTPmessages_GetChatInviteImporters::Flag;
_loadRequestId = _api.request(MTPmessages_GetChatInviteImporters(
MTP_flags(Flag::f_requested),
_peer->input,
MTPstring(), // link
MTPstring(), // q
MTP_int(_offsetDate),
_offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(),
MTP_int(limit)
)).done([=](const MTPmessages_ChatInviteImporters &result) {
const auto firstLoad = !_offsetDate;
_loadRequestId = 0;
result.match([&](const MTPDmessages_chatInviteImporters &data) {
session().data().processUsers(data.vusers());
const auto &importers = data.vimporters().v;
auto &owner = _peer->owner();
for (const auto &importer : importers) {
importer.match([&](const MTPDchatInviteImporter &data) {
_offsetDate = data.vdate().v;
_offsetUser = owner.user(data.vuser_id());
appendRow(_offsetUser, _offsetDate);
});
}
// To be sure - wait for a whole empty result list.
_allLoaded = importers.isEmpty();
});
if (_allLoaded
|| (firstLoad && delegate()->peerListFullRowsCount() > 0)) {
refreshDescription();
}
delegate()->peerListRefreshRows();
}).fail([=](const MTP::Error &error) {
_loadRequestId = 0;
_allLoaded = true;
}).send();
}
void RequestsBoxController::refreshDescription() {
setDescriptionText((delegate()->peerListFullRowsCount() > 0)
? QString()
: _peer->isBroadcast()
? tr::lng_group_requests_none_channel(tr::now)
: tr::lng_group_requests_none(tr::now));
}
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
_navigation->parentController()->show(PrepareShortInfoBox(
row->peer(),
_navigation));
}
void RequestsBoxController::rowElementClicked(
not_null<PeerListRow*> row,
int element) {
processRequest(row->peer()->asUser(), (element == kAcceptButton));
}
void RequestsBoxController::processRequest(
not_null<UserData*> user,
bool approved) {
const auto remove = [=] {
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
delegate()->peerListRemoveRow(row);
refreshDescription();
delegate()->peerListRefreshRows();
}
static_cast<RequestsBoxSearchController*>(
searchController())->removeFromCache(user);
};
const auto done = crl::guard(this, [=] {
remove();
if (approved) {
Ui::ShowMultilineToast({
.text = (_peer->isBroadcast()
? tr::lng_group_requests_was_added_channel
: tr::lng_group_requests_was_added)(
tr::now,
lt_user,
Ui::Text::Bold(user->name),
Ui::Text::WithEntities)
});
}
});
const auto fail = crl::guard(this, remove);
session().api().inviteLinks().processRequest(
_peer,
QString(), // link
user,
approved,
done,
fail);
}
void RequestsBoxController::appendRow(
not_null<UserData*> user,
TimeId date) {
if (!delegate()->peerListFindRow(user->id.value)) {
if (auto row = createRow(user, date)) {
delegate()->peerListAppendRow(std::move(row));
setDescriptionText(QString());
}
}
}
QSize RequestsBoxController::RowHelper::rowAcceptButtonSize() {
const auto &st = st::requestsAcceptButton;
return {
(st.width <= 0) ? (_acceptTextWidth - st.width) : st.width,
st.height,
};
}
QSize RequestsBoxController::RowHelper::rowRejectButtonSize() {
const auto &st = st::requestsRejectButton;
return {
(st.width <= 0) ? (_rejectTextWidth - st.width) : st.width,
st.height,
};
}
void RequestsBoxController::RowHelper::rowPaintAccept(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) {
paintButton(
p,
geometry,
st::requestsAcceptButton,
_acceptRect,
_acceptRectOver,
ripple,
_acceptText,
_acceptTextWidth,
outerWidth,
over);
}
void RequestsBoxController::RowHelper::rowPaintReject(
Painter &p,
QRect geometry,
std::unique_ptr<Ui::RippleAnimation> &ripple,
int outerWidth,
bool over) {
paintButton(
p,
geometry,
st::requestsRejectButton,
_rejectRect,
_rejectRectOver,
ripple,
_rejectText,
_rejectTextWidth,
outerWidth,
over);
}
void RequestsBoxController::RowHelper::paintButton(
Painter &p,
QRect geometry,
const style::RoundButton &st,
const Ui::RoundRect &rect,
const Ui::RoundRect &rectOver,
std::unique_ptr<Ui::RippleAnimation> &ripple,
const QString &text,
int textWidth,
int outerWidth,
bool over) {
rect.paint(p, geometry);
if (over) {
rectOver.paint(p, geometry);
}
if (ripple) {
ripple->paint(p, geometry.x(), geometry.y(), outerWidth);
if (ripple->empty()) {
ripple = nullptr;
}
}
const auto textLeft = geometry.x()
+ ((geometry.width() - textWidth) / 2);
const auto textTop = geometry.y() + st.textTop;
p.setFont(st.font);
p.setPen(over ? st.textFgOver : st.textFg);
p.drawTextLeft(textLeft, textTop, outerWidth, text);
}
std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
not_null<UserData*> user,
TimeId date) {
if (!date) {
const auto search = static_cast<RequestsBoxSearchController*>(
searchController());
date = search->dateForUser(user);
}
return std::make_unique<Row>(_helper.get(), user, date);
}
void RequestsBoxController::subscribeToMigration() {
const auto chat = _peer->asChat();
if (!chat) {
return;
}
SubscribeToMigration(
chat,
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(chat, channel); });
}
void RequestsBoxController::migrate(
not_null<ChatData*> chat,
not_null<ChannelData*> channel) {
_peer = channel;
}
RequestsBoxSearchController::RequestsBoxSearchController(
not_null<PeerData*> peer)
: _peer(peer)
, _api(&_peer->session().mtp()) {
_timer.setCallback([=] { searchOnServer(); });
}
void RequestsBoxSearchController::searchQuery(const QString &query) {
if (_query != query) {
_query = query;
_offsetDate = 0;
_offsetUser = nullptr;
_requestId = 0;
_allLoaded = false;
if (!_query.isEmpty() && !searchInCache()) {
_timer.callOnce(kServerSearchDelay);
} else {
_timer.cancel();
}
}
}
void RequestsBoxSearchController::searchOnServer() {
Expects(!_query.isEmpty());
loadMoreRows();
}
bool RequestsBoxSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
void RequestsBoxSearchController::removeFromCache(not_null<UserData*> user) {
for (auto &entry : _cache) {
auto &items = entry.second.items;
const auto j = ranges::remove(items, user, &Item::user);
if (j != end(items)) {
entry.second.requestedCount -= (end(items) - j);
items.erase(j, end(items));
}
}
}
TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
if (const auto i = _dates.find(user); i != end(_dates)) {
return i->second;
}
return {};
}
bool RequestsBoxSearchController::searchInCache() {
const auto i = _cache.find(_query);
if (i != _cache.cend()) {
_requestId = 0;
searchDone(
_requestId,
i->second.items,
i->second.requestedCount);
return true;
}
return false;
}
bool RequestsBoxSearchController::loadMoreRows() {
if (_query.isEmpty()) {
return false;
} else if (_allLoaded || isLoading()) {
return true;
}
// For search we request a lot of rows from the first query.
// (because we've waited for search request by timer already,
// so we don't expect it to be fast, but we want to fill cache).
const auto limit = kPerPage;
using Flag = MTPmessages_GetChatInviteImporters::Flag;
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
MTP_flags(Flag::f_requested | Flag::f_q),
_peer->input,
MTPstring(), // link
MTP_string(_query),
MTP_int(_offsetDate),
_offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(),
MTP_int(limit)
)).done([=](
const MTPmessages_ChatInviteImporters &result,
mtpRequestId requestId) {
auto items = std::vector<Item>();
result.match([&](const MTPDmessages_chatInviteImporters &data) {
const auto &importers = data.vimporters().v;
auto &owner = _peer->owner();
owner.processUsers(data.vusers());
items.reserve(importers.size());
for (const auto &importer : importers) {
importer.match([&](const MTPDchatInviteImporter &data) {
items.push_back({
owner.user(data.vuser_id()),
data.vdate().v,
});
});
}
});
searchDone(requestId, items, limit);
auto it = _queries.find(requestId);
if (it != _queries.cend()) {
const auto &query = it->second.text;
if (it->second.offsetDate == 0) {
auto &entry = _cache[query];
entry.items = std::move(items);
entry.requestedCount = limit;
}
_queries.erase(it);
}
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if (_requestId == requestId) {
_requestId = 0;
_allLoaded = true;
delegate()->peerListSearchRefreshRows();
}
}).send();
auto entry = Query();
entry.text = _query;
entry.offsetDate = _offsetDate;
_queries.emplace(_requestId, entry);
return true;
}
void RequestsBoxSearchController::searchDone(
mtpRequestId requestId,
const std::vector<Item> &items,
int requestedCount) {
if (_requestId != requestId) {
return;
}
_requestId = 0;
if (!_offsetDate) {
_dates.clear();
}
for (const auto &[user, date] : items) {
_offsetDate = date;
_offsetUser = user;
_dates.emplace(user, date);
delegate()->peerListSearchAddRow(user);
}
if (items.size() < requestedCount) {
// We want cache to have full information about a query with
// small results count (that we don't need the second request).
// So we don't wait for empty list unlike the non-search case.
_allLoaded = true;
}
delegate()->peerListSearchRefreshRows();
}

View File

@@ -0,0 +1,119 @@
/*
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
#include "boxes/peer_list_box.h"
#include "base/weak_ptr.h"
namespace Window {
class SessionNavigation;
} // namespace Window
namespace Ui {
class RippleAnimation;
} // namespace Ui
class RequestsBoxController final
: public PeerListController
, public base::has_weak_ptr {
public:
RequestsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);
~RequestsBoxController();
static void Start(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override;
private:
class RowHelper;
static std::unique_ptr<PeerListSearchController> CreateSearchController(
not_null<PeerData*> peer);
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user,
TimeId date = 0);
void appendRow(not_null<UserData*> user, TimeId date);
void refreshDescription();
void processRequest(not_null<UserData*> user, bool approved);
void subscribeToMigration();
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
const not_null<Window::SessionNavigation*> _navigation;
const std::unique_ptr<RowHelper> _helper;
not_null<PeerData*> _peer;
MTP::Sender _api;
TimeId _offsetDate = 0;
UserData *_offsetUser = nullptr;
mtpRequestId _loadRequestId = 0;
bool _allLoaded = false;
};
// Members, banned and restricted users server side search.
class RequestsBoxSearchController final : public PeerListSearchController {
public:
RequestsBoxSearchController(not_null<PeerData*> peer);
void searchQuery(const QString &query) override;
bool isLoading() override;
bool loadMoreRows() override;
void removeFromCache(not_null<UserData*> user);
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
private:
struct Item {
not_null<UserData*> user;
TimeId date = 0;
};
struct CacheEntry {
std::vector<Item> items;
int requestedCount = 0;
};
struct Query {
QString text;
TimeId offsetDate = 0;
UserData *offsetUser = nullptr;
};
void searchOnServer();
bool searchInCache();
void searchDone(
mtpRequestId requestId,
const std::vector<Item> &items,
int requestedCount);
not_null<PeerData*> _peer;
MTP::Sender _api;
base::Timer _timer;
QString _query;
mtpRequestId _requestId = 0;
TimeId _offsetDate = 0;
UserData *_offsetUser = nullptr;
bool _allLoaded = false;
base::flat_map<QString, CacheEntry> _cache;
base::flat_map<mtpRequestId, Query> _queries;
base::flat_map<not_null<UserData*>, TimeId> _dates;
};

View File

@@ -11,9 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_invite_links.h"
#include "main/main_session.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_peer_info_box.h" // CreateButton.
#include "boxes/peers/edit_peer_invite_link.h"
#include "boxes/peers/edit_peer_invite_links.h"
@@ -55,9 +56,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kUsernameCheckTimeout = crl::time(200);
constexpr auto kMinUsernameLength = 5;
class Controller : public base::has_weak_ptr {
public:
Controller(
@@ -419,7 +417,7 @@ void Controller::checkUsernameAvailability() {
const auto checking = initial
? qsl(".bad.")
: getUsernameInput();
if (checking.size() < kMinUsernameLength) {
if (checking.size() < Ui::EditPeer::kMinUsernameLength) {
return;
}
if (_checkUsernameRequestId) {
@@ -496,11 +494,11 @@ void Controller::usernameChanged() {
});
if (bad) {
showUsernameError(tr::lng_create_channel_link_bad_symbols());
} else if (username.size() < kMinUsernameLength) {
} else if (username.size() < Ui::EditPeer::kMinUsernameLength) {
showUsernameError(tr::lng_create_channel_link_too_short());
} else {
_controls.usernameResult = nullptr;
_checkUsernameTimer.callOnce(kUsernameCheckTimeout);
_checkUsernameTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
}
}

View File

@@ -0,0 +1,808 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/peer_short_info_box.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/wrap.h"
#include "ui/image/image_prepare.h"
#include "ui/text/text_utilities.h"
#include "info/profile/info_profile_text.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "base/event_filter.h"
#include "lang/lang_keys.h"
#include "styles/style_layers.h"
#include "styles/style_info.h"
namespace {
constexpr auto kShadowMaxAlpha = 80;
constexpr auto kInactiveBarOpacity = 0.5;
} // namespace
struct PeerShortInfoCover::CustomLabelStyle {
explicit CustomLabelStyle(const style::FlatLabel &original);
style::complex_color textFg;
style::FlatLabel st;
float64 opacity = 1.;
};
struct PeerShortInfoCover::Radial {
explicit Radial(Fn<void()> &&callback);
void toggle(bool visible);
Ui::RadialAnimation radial;
Ui::Animations::Simple shownAnimation;
Fn<void()> callback;
base::Timer showTimer;
bool shown = false;
};
PeerShortInfoCover::Radial::Radial(Fn<void()> &&callback)
: radial(callback)
, callback(callback)
, showTimer([=] { toggle(true); }) {
}
void PeerShortInfoCover::Radial::toggle(bool visible) {
if (shown == visible) {
return;
}
shown = visible;
shownAnimation.start(
callback,
shown ? 0. : 1.,
shown ? 1. : 0.,
st::fadeWrapDuration);
}
PeerShortInfoCover::CustomLabelStyle::CustomLabelStyle(
const style::FlatLabel &original)
: textFg([=, c = original.textFg]{
auto result = c->c;
result.setAlphaF(result.alphaF() * opacity);
return result;
})
, st(original) {
st.textFg = textFg.color();
}
PeerShortInfoCover::PeerShortInfoCover(
not_null<QWidget*> parent,
const style::ShortInfoCover &st,
rpl::producer<QString> name,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic,
Fn<bool()> videoPaused)
: _st(st)
, _owned(parent.get())
, _widget(_owned.data())
, _nameStyle(std::make_unique<CustomLabelStyle>(_st.name))
, _name(_widget.get(), std::move(name), _nameStyle->st)
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
, _status(_widget.get(), std::move(status), _statusStyle->st)
, _videoPaused(std::move(videoPaused)) {
_widget->setCursor(_cursor);
_widget->resize(_st.size, _st.size);
std::move(
userpic
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
applyUserpic(std::move(value));
}, lifetime());
style::PaletteChanged(
) | rpl::start_with_next([=] {
refreshBarImages();
}, lifetime());
_widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(_widget.get());
paint(p);
}, lifetime());
base::install_event_filter(_widget.get(), [=](not_null<QEvent*> e) {
if (e->type() != QEvent::MouseButtonPress
&& e->type() != QEvent::MouseButtonDblClick) {
return base::EventFilterResult::Continue;
}
const auto mouse = static_cast<QMouseEvent*>(e.get());
const auto x = mouse->pos().x();
if (mouse->button() != Qt::LeftButton) {
return base::EventFilterResult::Continue;
} else if (/*_index > 0 && */x < _st.size / 3) {
_moveRequests.fire(-1);
} else if (/*_index + 1 < _count && */x >= _st.size / 3) {
_moveRequests.fire(1);
}
e->accept();
return base::EventFilterResult::Cancel;
});
_name->moveToLeft(
_st.namePosition.x(),
_st.size - _st.namePosition.y() - _name->height(),
_st.size);
_status->moveToLeft(
_st.statusPosition.x(),
(_st.size
- _st.statusPosition.y()
- _status->height()),
_st.size);
_roundedTopImage = QImage(
QSize(_st.size, st::boxRadius) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
_roundedTopImage.setDevicePixelRatio(style::DevicePixelRatio());
_roundedTopImage.fill(Qt::transparent);
}
PeerShortInfoCover::~PeerShortInfoCover() = default;
not_null<Ui::RpWidget*> PeerShortInfoCover::widget() const {
return _widget;
}
object_ptr<Ui::RpWidget> PeerShortInfoCover::takeOwned() {
return std::move(_owned);
}
void PeerShortInfoCover::setScrollTop(int scrollTop) {
_scrollTop = scrollTop;
_widget->update();
}
rpl::producer<int> PeerShortInfoCover::moveRequests() const {
return _moveRequests.events();
}
rpl::lifetime &PeerShortInfoCover::lifetime() {
return _widget->lifetime();
}
void PeerShortInfoCover::paint(QPainter &p) {
checkStreamedIsStarted();
const auto frame = currentVideoFrame();
auto paused = _videoPaused && _videoPaused();
if (frame.isNull() && _userpicImage.isNull()) {
auto image = QImage(
_widget->size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
Images::prepareRound(
image,
ImageRoundRadius::Small,
RectPart::TopLeft | RectPart::TopRight);
_userpicImage = std::move(image);
}
paintCoverImage(p, frame.isNull() ? _userpicImage : frame);
paintBars(p);
paintShadow(p);
paintRadial(p);
if (_videoInstance && _videoInstance->ready() && !paused) {
_videoInstance->markFrameShown();
}
}
void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
const auto roundedWidth = _st.size;
const auto roundedHeight = st::boxRadius;
const auto covered = (_st.size - _scrollTop);
if (covered <= 0) {
return;
} else if (!_scrollTop) {
p.drawImage(_widget->rect(), image);
return;
}
const auto fill = covered - roundedHeight;
const auto top = _widget->height() - fill;
const auto factor = style::DevicePixelRatio();
if (fill > 0) {
p.drawImage(
QRect(0, top, roundedWidth, fill),
image,
QRect(0, top * factor, roundedWidth * factor, fill * factor));
}
if (covered <= 0) {
return;
}
const auto rounded = std::min(covered, roundedHeight);
const auto from = top - rounded;
auto q = QPainter(&_roundedTopImage);
q.drawImage(
QRect(0, 0, roundedWidth, rounded),
image,
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
q.end();
Images::prepareRound(
_roundedTopImage,
ImageRoundRadius::Small,
RectPart::TopLeft | RectPart::TopRight);
p.drawImage(
QRect(0, from, roundedWidth, rounded),
_roundedTopImage,
QRect(0, 0, roundedWidth * factor, rounded * factor));
}
void PeerShortInfoCover::paintBars(QPainter &p) {
const auto height = _st.linePadding * 2 + _st.line;
const auto factor = style::DevicePixelRatio();
if (_shadowTop.isNull()) {
_shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
_shadowTop = _shadowTop.scaled(QSize(_st.size, height) * factor);
Images::prepareRound(
_shadowTop,
ImageRoundRadius::Small,
RectPart::TopLeft | RectPart::TopRight);
}
const auto shadowRect = QRect(0, _scrollTop, _st.size, height);
p.drawImage(
shadowRect,
_shadowTop,
QRect(0, 0, _shadowTop.width(), height * factor));
const auto hiddenAt = _st.size - _st.namePosition.y();
if (!_smallWidth || _scrollTop >= hiddenAt) {
return;
}
const auto start = _st.linePadding;
const auto y = _scrollTop + start;
const auto skip = _st.lineSkip;
const auto full = (_st.size - 2 * start - (_count - 1) * skip);
const auto single = full / float64(_count);
const auto masterOpacity = 1. - (_scrollTop / float64(hiddenAt));
const auto inactiveOpacity = masterOpacity * kInactiveBarOpacity;
for (auto i = 0; i != _count; ++i) {
const auto left = start + i * (single + skip);
const auto right = left + single;
const auto x = qRound(left);
const auto small = (qRound(right) == qRound(left) + _smallWidth);
const auto width = small ? _smallWidth : _largeWidth;
const auto &image = small ? _barSmall : _barLarge;
const auto min = 2 * ((_st.line + 1) / 2);
const auto minProgress = min / float64(width);
const auto videoProgress = (_videoInstance && _videoDuration > 0);
const auto progress = (i != _index)
? 0.
: videoProgress
? std::max(_videoPosition / float64(_videoDuration), minProgress)
: (_videoInstance ? 0. : 1.);
if (progress == 1. && !videoProgress) {
p.setOpacity(masterOpacity);
p.drawImage(x, y, image);
} else {
p.setOpacity(inactiveOpacity);
p.drawImage(x, y, image);
if (progress > 0.) {
const auto paint = qRound(progress * width);
const auto right = paint / 2;
const auto left = paint - right;
p.setOpacity(masterOpacity);
p.drawImage(
QRect(x, y, left, _st.line),
image,
QRect(0, 0, left * factor, image.height()));
p.drawImage(
QRect(x + left, y, right, _st.line),
image,
QRect(left * factor, 0, right * factor, image.height()));
}
}
}
p.setOpacity(1.);
}
void PeerShortInfoCover::paintShadow(QPainter &p) {
if (_shadowBottom.isNull()) {
_shadowBottom = Images::GenerateShadow(
_st.shadowHeight,
0,
kShadowMaxAlpha);
}
const auto shadowTop = _st.size - _st.shadowHeight;
if (_scrollTop >= shadowTop) {
_name->hide();
_status->hide();
return;
}
const auto opacity = 1. - (_scrollTop / float64(shadowTop));
_nameStyle->opacity = opacity;
_nameStyle->textFg.refresh();
_name->show();
_statusStyle->opacity = opacity;
_statusStyle->textFg.refresh();
_status->show();
p.setOpacity(opacity);
const auto shadowRect = QRect(
0,
shadowTop,
_st.size,
_st.shadowHeight);
const auto factor = style::DevicePixelRatio();
p.drawImage(
shadowRect,
_shadowBottom,
QRect(
0,
0,
_shadowBottom.width(),
_st.shadowHeight * factor));
p.setOpacity(1.);
}
void PeerShortInfoCover::paintRadial(QPainter &p) {
const auto infinite = _videoInstance && _videoInstance->waitingShown();
if (!_radial && !infinite) {
return;
}
const auto radial = radialRect();
const auto line = _st.radialAnimation.thickness;
const auto arc = radial.marginsRemoved(
{ line, line, line, line });
const auto infiniteOpacity = _videoInstance
? _videoInstance->waitingOpacity()
: 0.;
const auto radialState = _radial
? _radial->radial.computeState()
: Ui::RadialState();
if (_radial) {
updateRadialState();
}
const auto radialOpacity = _radial
? (_radial->shownAnimation.value(_radial->shown ? 1. : 0.)
* radialState.shown)
: 0.;
auto hq = PainterHighQualityEnabler(p);
p.setOpacity(std::max(infiniteOpacity, radialOpacity));
p.setPen(Qt::NoPen);
p.setBrush(st::radialBg);
p.drawEllipse(radial);
if (radialOpacity > 0.) {
p.setOpacity(radialOpacity);
auto pen = _st.radialAnimation.color->p;
pen.setWidth(line);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
p.drawArc(arc, radialState.arcFrom, radialState.arcLength);
}
if (infinite) {
p.setOpacity(1.);
Ui::InfiniteRadialAnimation::Draw(
p,
_videoInstance->waitingState(),
arc.topLeft(),
arc.size(),
_st.size,
_st.radialAnimation.color,
line);
}
}
QImage PeerShortInfoCover::currentVideoFrame() const {
const auto size = QSize(_st.size, _st.size);
const auto request = Media::Streaming::FrameRequest{
.resize = size * style::DevicePixelRatio(),
.outer = size,
.radius = ImageRoundRadius::Small,
.corners = RectPart::TopLeft | RectPart::TopRight,
};
return (_videoInstance
&& _videoInstance->player().ready()
&& !_videoInstance->player().videoSize().isEmpty())
? _videoInstance->frame(request)
: QImage();
}
void PeerShortInfoCover::applyUserpic(PeerShortInfoUserpic &&value) {
if (_index != value.index) {
_index = value.index;
_widget->update();
}
if (_count != value.count) {
_count = value.count;
refreshCoverCursor();
refreshBarImages();
_widget->update();
}
if (value.photo.isNull()) {
const auto videoChanged = _videoInstance
? (_videoInstance->shared() != value.videoDocument)
: (value.videoDocument != nullptr);
const auto frame = videoChanged ? currentVideoFrame() : QImage();
if (!frame.isNull()) {
_userpicImage = frame;
}
} else if (_userpicImage.cacheKey() != value.photo.cacheKey()) {
_userpicImage = std::move(value.photo);
_widget->update();
}
if (!value.videoDocument) {
clearVideo();
} else if (!_videoInstance
|| _videoInstance->shared() != value.videoDocument) {
using namespace Media::Streaming;
_videoInstance = std::make_unique<Instance>(
std::move(value.videoDocument),
[=] { videoWaiting(); });
_videoStartPosition = value.videoStartPosition;
_videoInstance->lockPlayer();
_videoInstance->player().updates(
) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update));
}, [=](Error &&error) {
handleStreamingError(std::move(error));
}, _videoInstance->lifetime());
if (_videoInstance->ready()) {
streamingReady(base::duplicate(_videoInstance->info()));
}
if (!_videoInstance->valid()) {
clearVideo();
}
}
_photoLoadingProgress = value.photoLoadingProgress;
updateRadialState();
}
void PeerShortInfoCover::updateRadialState() {
const auto progress = _videoInstance ? 1. : _photoLoadingProgress;
if (_radial) {
_radial->radial.update(progress, (progress == 1.), crl::now());
}
_widget->update(radialRect());
if (progress == 1.) {
if (!_radial) {
return;
}
_radial->showTimer.cancel();
_radial->toggle(false);
if (!_radial->shownAnimation.animating()) {
_radial = nullptr;
}
return;
} else if (!_radial) {
_radial = std::make_unique<Radial>([=] { updateRadialState(); });
_radial->radial.update(progress, false, crl::now());
_radial->showTimer.callOnce(st::fadeWrapDuration);
return;
} else if (!_radial->showTimer.isActive()) {
_radial->toggle(true);
}
}
void PeerShortInfoCover::clearVideo() {
_videoInstance = nullptr;
_videoStartPosition = _videoPosition = _videoDuration = 0;
}
void PeerShortInfoCover::checkStreamedIsStarted() {
if (!_videoInstance) {
return;
} else if (_videoInstance->paused()) {
_videoInstance->resume();
}
if (!_videoInstance
|| _videoInstance->active()
|| _videoInstance->failed()) {
return;
}
auto options = Media::Streaming::PlaybackOptions();
options.position = _videoStartPosition;
options.mode = Media::Streaming::Mode::Video;
options.loop = true;
_videoInstance->play(options);
}
void PeerShortInfoCover::handleStreamingUpdate(
Media::Streaming::Update &&update) {
using namespace Media::Streaming;
v::match(update.data, [&](Information &update) {
streamingReady(std::move(update));
}, [&](const PreloadedVideo &update) {
}, [&](const UpdateVideo &update) {
_videoPosition = update.position;
_widget->update();
}, [&](const PreloadedAudio &update) {
}, [&](const UpdateAudio &update) {
}, [&](const WaitingForData &update) {
}, [&](MutedByOther) {
}, [&](Finished) {
});
}
void PeerShortInfoCover::handleStreamingError(
Media::Streaming::Error &&error) {
//_streamedPhoto->setVideoPlaybackFailed();
//_streamedPhoto = nullptr;
clearVideo();
}
void PeerShortInfoCover::streamingReady(Media::Streaming::Information &&info) {
_videoPosition = info.video.state.position;
_videoDuration = info.video.state.duration;
_widget->update();
}
void PeerShortInfoCover::refreshCoverCursor() {
const auto cursor = (_count > 1)
? style::cur_pointer
: style::cur_default;
if (_cursor != cursor) {
_cursor = cursor;
_widget->setCursor(_cursor);
}
}
void PeerShortInfoCover::refreshBarImages() {
if (_count < 2) {
_smallWidth = _largeWidth = 0;
_barSmall = _barLarge = QImage();
return;
}
const auto width = _st.size - 2 * _st.linePadding;
_smallWidth = (width - (_count - 1) * _st.lineSkip) / _count;
if (_smallWidth < _st.line) {
_smallWidth = _largeWidth = 0;
_barSmall = _barLarge = QImage();
return;
}
_largeWidth = _smallWidth + 1;
const auto makeBar = [&](int size) {
const auto radius = _st.line / 2.;
auto result = QImage(
QSize(size, _st.line) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(style::DevicePixelRatio());
result.fill(Qt::transparent);
auto p = QPainter(&result);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::groupCallVideoTextFg);
p.drawRoundedRect(0, 0, size, _st.line, radius, radius);
p.end();
return result;
};
_barSmall = makeBar(_smallWidth);
_barLarge = makeBar(_largeWidth);
}
QRect PeerShortInfoCover::radialRect() const {
const auto cover = _widget->rect();
const auto size = st::boxLoadingSize;
return QRect(
cover.x() + (cover.width() - size) / 2,
cover.y() + (cover.height() - size) / 2,
size,
size);
}
void PeerShortInfoCover::videoWaiting() {
if (!anim::Disabled()) {
_widget->update(radialRect());
}
}
PeerShortInfoBox::PeerShortInfoBox(
QWidget*,
PeerShortInfoType type,
rpl::producer<PeerShortInfoFields> fields,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic,
Fn<bool()> videoPaused)
: _type(type)
, _fields(std::move(fields))
, _topRoundBackground(this)
, _scroll(this, st::shortInfoScroll)
, _rows(
_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(
_scroll.data())))
, _cover(
_rows.get(),
st::shortInfoCover,
nameValue(),
std::move(status),
std::move(userpic),
std::move(videoPaused)) {
_rows->add(_cover.takeOwned());
_scroll->scrolls(
) | rpl::start_with_next([=] {
_cover.setScrollTop(_scroll->scrollTop());
}, _cover.lifetime());
}
PeerShortInfoBox::~PeerShortInfoBox() = default;
rpl::producer<> PeerShortInfoBox::openRequests() const {
return _openRequests.events();
}
rpl::producer<int> PeerShortInfoBox::moveRequests() const {
return _cover.moveRequests();
}
void PeerShortInfoBox::prepare() {
addButton(tr::lng_close(), [=] { closeBox(); });
// Perhaps a new lang key should be added for opening a group.
addLeftButton((_type == PeerShortInfoType::User)
? tr::lng_profile_send_message()
: (_type == PeerShortInfoType::Group)
? tr::lng_view_button_group()
: tr::lng_profile_view_channel(), [=] { _openRequests.fire({}); });
prepareRows();
setNoContentMargin(true);
_topRoundBackground->resize(st::shortInfoWidth, st::boxRadius);
_topRoundBackground->paintRequest(
) | rpl::start_with_next([=] {
if (const auto use = fillRoundedTopHeight()) {
const auto width = _topRoundBackground->width();
const auto top = _topRoundBackground->height() - use;
const auto factor = style::DevicePixelRatio();
QPainter(_topRoundBackground.data()).drawImage(
QRect(0, top, width, use),
_roundedTop,
QRect(0, top * factor, width * factor, use * factor));
}
}, _topRoundBackground->lifetime());
_roundedTop = QImage(
_topRoundBackground->size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
refreshRoundedTopImage(getDelegate()->style().bg->c);
setDimensionsToContent(st::shortInfoWidth, _rows);
}
void PeerShortInfoBox::prepareRows() {
using namespace Info::Profile;
auto addInfoLineGeneric = [&](
rpl::producer<QString> &&label,
rpl::producer<TextWithEntities> &&text,
const style::FlatLabel &textSt = st::infoLabeled) {
auto line = CreateTextWithLabel(
_rows,
rpl::duplicate(label) | Ui::Text::ToWithEntities(),
rpl::duplicate(text),
textSt,
st::shortInfoLabeledPadding);
_rows->add(object_ptr<Ui::OverrideMargins>(
_rows.get(),
std::move(line.wrap)));
rpl::combine(
std::move(label),
std::move(text)
) | rpl::start_with_next([=] {
_rows->resizeToWidth(st::shortInfoWidth);
}, _rows->lifetime());
//line.text->setClickHandlerFilter(infoClickFilter);
return line.text;
};
auto addInfoLine = [&](
rpl::producer<QString> &&label,
rpl::producer<TextWithEntities> &&text,
const style::FlatLabel &textSt = st::infoLabeled) {
return addInfoLineGeneric(
std::move(label),
std::move(text),
textSt);
};
auto addInfoOneLine = [&](
rpl::producer<QString> &&label,
rpl::producer<TextWithEntities> &&text,
const QString &contextCopyText) {
auto result = addInfoLine(
std::move(label),
std::move(text),
st::infoLabeledOneLine);
result->setDoubleClickSelectsParagraph(true);
result->setContextCopyText(contextCopyText);
return result;
};
addInfoOneLine(
tr::lng_info_link_label(),
linkValue(),
tr::lng_context_copy_link(tr::now));
addInfoOneLine(
tr::lng_info_mobile_label(),
phoneValue() | Ui::Text::ToWithEntities(),
tr::lng_profile_copy_phone(tr::now));
auto label = _fields.current().isBio
? tr::lng_info_bio_label()
: tr::lng_info_about_label();
addInfoLine(std::move(label), aboutValue());
addInfoOneLine(
tr::lng_info_username_label(),
usernameValue() | Ui::Text::ToWithEntities(),
tr::lng_context_copy_mention(tr::now));
}
RectParts PeerShortInfoBox::customCornersFilling() {
return RectPart::FullTop;
}
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_rows->resizeToWidth(st::shortInfoWidth);
_scroll->resize(st::shortInfoWidth, height());
_scroll->move(0, 0);
_topRoundBackground->move(0, 0);
}
int PeerShortInfoBox::fillRoundedTopHeight() {
const auto roundedHeight = _topRoundBackground->height();
const auto scrollTop = _scroll->scrollTop();
const auto covered = (st::shortInfoWidth - scrollTop);
if (covered >= roundedHeight) {
return 0;
}
const auto &color = getDelegate()->style().bg->c;
if (_roundedTopColor != color) {
refreshRoundedTopImage(color);
}
return roundedHeight - covered;
}
void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
_roundedTopColor = color;
_roundedTop.fill(color);
Images::prepareRound(
_roundedTop,
ImageRoundRadius::Small,
RectPart::TopLeft | RectPart::TopRight);
}
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return fields.name;
}) | rpl::distinct_until_changed();
}
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return Ui::Text::Link(fields.link, fields.link);
}) | rpl::distinct_until_changed();
}
rpl::producer<QString> PeerShortInfoBox::phoneValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return fields.phone;
}) | rpl::distinct_until_changed();
}
rpl::producer<QString> PeerShortInfoBox::usernameValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return fields.username;
}) | rpl::distinct_until_changed();
}
rpl::producer<TextWithEntities> PeerShortInfoBox::aboutValue() const {
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
return fields.about;
}) | rpl::distinct_until_changed();
}

View File

@@ -0,0 +1,180 @@
/*
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
#include "boxes/abstract_box.h"
namespace style {
struct ShortInfoCover;
} // namespace style
namespace Media::Streaming {
class Document;
class Instance;
struct Update;
enum class Error;
struct Information;
} // namespace Media::Streaming
namespace Ui {
class VerticalLayout;
class RpWidget;
} // namespace Ui
enum class PeerShortInfoType {
User,
Group,
Channel,
};
struct PeerShortInfoFields {
QString name;
QString phone;
QString link;
TextWithEntities about;
QString username;
bool isBio = false;
};
struct PeerShortInfoUserpic {
int index = 0;
int count = 0;
QImage photo;
float64 photoLoadingProgress = 0.;
std::shared_ptr<Media::Streaming::Document> videoDocument;
crl::time videoStartPosition = 0;
};
class PeerShortInfoCover final {
public:
PeerShortInfoCover(
not_null<QWidget*> parent,
const style::ShortInfoCover &st,
rpl::producer<QString> name,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic,
Fn<bool()> videoPaused);
~PeerShortInfoCover();
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
[[nodiscard]] object_ptr<Ui::RpWidget> takeOwned();
void setScrollTop(int scrollTop);
[[nodiscard]] rpl::producer<int> moveRequests() const;
[[nodiscard]] rpl::lifetime &lifetime();
private:
struct CustomLabelStyle;
struct Radial;
void paint(QPainter &p);
void paintCoverImage(QPainter &p, const QImage &image);
void paintBars(QPainter &p);
void paintShadow(QPainter &p);
void paintRadial(QPainter &p);
[[nodiscard]] QImage currentVideoFrame() const;
void applyUserpic(PeerShortInfoUserpic &&value);
[[nodiscard]] QRect radialRect() const;
void videoWaiting();
void checkStreamedIsStarted();
void handleStreamingUpdate(Media::Streaming::Update &&update);
void handleStreamingError(Media::Streaming::Error &&error);
void streamingReady(Media::Streaming::Information &&info);
void clearVideo();
void updateRadialState();
void refreshCoverCursor();
void refreshBarImages();
const style::ShortInfoCover &_st;
object_ptr<Ui::RpWidget> _owned;
const not_null<Ui::RpWidget*> _widget;
std::unique_ptr<CustomLabelStyle> _nameStyle;
object_ptr<Ui::FlatLabel> _name;
std::unique_ptr<CustomLabelStyle> _statusStyle;
object_ptr<Ui::FlatLabel> _status;
QImage _userpicImage;
QImage _roundedTopImage;
QImage _barSmall;
QImage _barLarge;
QImage _shadowTop;
int _scrollTop = 0;
int _smallWidth = 0;
int _largeWidth = 0;
int _index = 0;
int _count = 0;
style::cursor _cursor = style::cur_default;
std::unique_ptr<Media::Streaming::Instance> _videoInstance;
crl::time _videoStartPosition = 0;
crl::time _videoPosition = 0;
crl::time _videoDuration = 0;
Fn<bool()> _videoPaused;
QImage _shadowBottom;
std::unique_ptr<Radial> _radial;
float64 _photoLoadingProgress = 0.;
rpl::event_stream<int> _moveRequests;
};
class PeerShortInfoBox final : public Ui::BoxContent {
public:
PeerShortInfoBox(
QWidget*,
PeerShortInfoType type,
rpl::producer<PeerShortInfoFields> fields,
rpl::producer<QString> status,
rpl::producer<PeerShortInfoUserpic> userpic,
Fn<bool()> videoPaused);
~PeerShortInfoBox();
[[nodiscard]] rpl::producer<> openRequests() const;
[[nodiscard]] rpl::producer<int> moveRequests() const;
private:
void prepare() override;
void prepareRows();
RectParts customCornersFilling() override;
void resizeEvent(QResizeEvent *e) override;
void refreshRoundedTopImage(const QColor &color);
int fillRoundedTopHeight();
[[nodiscard]] rpl::producer<QString> nameValue() const;
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
[[nodiscard]] rpl::producer<QString> phoneValue() const;
[[nodiscard]] rpl::producer<QString> usernameValue() const;
[[nodiscard]] rpl::producer<TextWithEntities> aboutValue() const;
const PeerShortInfoType _type = PeerShortInfoType::User;
rpl::variable<PeerShortInfoFields> _fields;
QColor _roundedTopColor;
QImage _roundedTop;
object_ptr<Ui::RpWidget> _topRoundBackground;
object_ptr<Ui::ScrollArea> _scroll;
not_null<Ui::VerticalLayout*> _rows;
PeerShortInfoCover _cover;
rpl::event_stream<> _openRequests;
};

View File

@@ -0,0 +1,465 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peers/peer_short_info_box.h"
#include "data/data_peer.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer_values.h"
#include "data/data_user_photos.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "info/profile/info_profile_values.h"
#include "ui/text/format_values.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "styles/style_info.h"
namespace {
constexpr auto kOverviewLimit = 48;
struct UserpicState {
PeerShortInfoUserpic current;
std::optional<UserPhotosSlice> userSlice;
PhotoId userpicPhotoId = PeerData::kUnknownPhotoId;
std::shared_ptr<Data::CloudImageView> userpicView;
std::shared_ptr<Data::PhotoMedia> photoView;
std::vector<std::shared_ptr<Data::PhotoMedia>> photoPreloads;
InMemoryKey userpicKey;
PhotoId photoId = PeerData::kUnknownPhotoId;
bool waitingFull = false;
bool waitingLoad = false;
};
void GenerateImage(
not_null<UserpicState*> state,
QImage image,
bool blurred = false) {
using namespace Images;
const auto size = st::shortInfoWidth;
const auto factor = style::DevicePixelRatio();
const auto options = Option::Smooth
| Option::RoundedSmall
| Option::RoundedTopLeft
| Option::RoundedTopRight
| (blurred ? Option::Blurred : Option());
state->current.photo = Images::prepare(
std::move(image),
size * factor,
size * factor,
options,
size,
size);
}
void GenerateImage(
not_null<UserpicState*> state,
not_null<Image*> image,
bool blurred = false) {
GenerateImage(state, image->original(), blurred);
}
void ProcessUserpic(
not_null<PeerData*> peer,
not_null<UserpicState*> state) {
state->current.videoDocument = nullptr;
state->userpicKey = peer->userpicUniqueKey(state->userpicView);
if (!state->userpicView) {
GenerateImage(
state,
peer->generateUserpicImage(
state->userpicView,
st::shortInfoWidth * style::DevicePixelRatio(),
ImageRoundRadius::None),
false);
state->current.photoLoadingProgress = 1.;
state->photoView = nullptr;
return;
}
peer->loadUserpic();
const auto image = state->userpicView->image();
if (!image) {
state->current.photoLoadingProgress = 0.;
state->current.photo = QImage();
state->waitingLoad = true;
return;
}
GenerateImage(state, image, true);
state->current.photoLoadingProgress = peer->userpicPhotoId() ? 0. : 1.;
state->photoView = nullptr;
}
void Preload(
not_null<PeerData*> peer,
not_null<UserpicState*> state) {
auto taken = base::take(state->photoPreloads);
if (state->userSlice && state->userSlice->size() > 0) {
const auto preload = [&](int index) {
const auto photo = peer->owner().photo(
(*state->userSlice)[index]);
const auto current = (peer->userpicPhotoId() == photo->id);
const auto origin = current
? peer->userpicPhotoOrigin()
: Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id);
state->photoPreloads.push_back(photo->createMediaView());
if (photo->hasVideo()) {
state->photoPreloads.back()->videoWanted(origin);
} else {
state->photoPreloads.back()->wanted(
Data::PhotoSize::Large,
origin);
}
};
const auto skip = (state->userSlice->size() == state->current.count)
? 0
: 1;
if (state->current.index - skip > 0) {
preload(state->current.index - skip - 1);
} else if (!state->current.index && state->current.count > 1) {
preload(state->userSlice->size() - 1);
}
if (state->current.index - skip + 1 < state->userSlice->size()) {
preload(state->current.index - skip + 1);
} else if (!skip && state->current.index > 0) {
preload(0);
}
}
}
void ProcessFullPhoto(
not_null<PeerData*> peer,
not_null<UserpicState*> state,
not_null<PhotoData*> photo) {
using PhotoSize = Data::PhotoSize;
const auto current = (peer->userpicPhotoId() == photo->id);
const auto video = photo->hasVideo();
const auto originCurrent = peer->userpicPhotoOrigin();
const auto originOther = peer->isUser()
? Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id)
: originCurrent;
const auto origin = current ? originCurrent : originOther;
const auto was = base::take(state->current.videoDocument);
const auto view = photo->createMediaView();
if (!video) {
view->wanted(PhotoSize::Large, origin);
}
if (const auto image = view->image(PhotoSize::Large)) {
GenerateImage(state, image);
Preload(peer, state);
state->photoView = nullptr;
state->current.photoLoadingProgress = 1.;
} else {
if (const auto thumbnail = view->image(PhotoSize::Thumbnail)) {
GenerateImage(state, thumbnail, true);
} else if (const auto small = view->image(PhotoSize::Small)) {
GenerateImage(state, small, true);
} else {
if (current) {
ProcessUserpic(peer, state);
}
if (!current || state->current.photo.isNull()) {
if (const auto blurred = view->thumbnailInline()) {
GenerateImage(state, blurred, true);
} else {
state->current.photo = QImage();
}
}
}
state->waitingLoad = !video;
state->photoView = view;
state->current.photoLoadingProgress = photo->progress();
}
if (!video) {
return;
}
state->current.videoDocument = peer->owner().streaming().sharedDocument(
photo,
origin);
state->current.videoStartPosition = photo->videoStartPosition();
state->photoView = nullptr;
state->current.photoLoadingProgress = 1.;
}
} // namespace
[[nodiscard]] rpl::producer<PeerShortInfoFields> FieldsValue(
not_null<PeerData*> peer) {
using UpdateFlag = Data::PeerUpdate::Flag;
return peer->session().changes().peerFlagsValue(
peer,
(UpdateFlag::Name
| UpdateFlag::PhoneNumber
| UpdateFlag::Username
| UpdateFlag::About)
) | rpl::map([=] {
const auto user = peer->asUser();
const auto username = peer->userName();
return PeerShortInfoFields{
.name = peer->name,
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
.link = ((user || username.isEmpty())
? QString()
: peer->session().createInternalLinkFull(username)),
.about = Info::Profile::AboutWithEntities(peer, peer->about()),
.username = ((user && !username.isEmpty())
? ('@' + username)
: QString()),
.isBio = (user && !user->isBot()),
};
});
}
[[nodiscard]] rpl::producer<QString> StatusValue(not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
const auto now = base::unixtime::now();
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto timer = lifetime.make_state<base::Timer>();
const auto push = [=] {
consumer.put_next(Data::OnlineText(user, now));
timer->callOnce(Data::OnlineChangeTimeout(user, now));
};
timer->setCallback(push);
push();
return lifetime;
};
}
return peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::Members
) | rpl::map([=] {
const auto chat = peer->asChat();
const auto channel = peer->asChannel();
const auto count = std::max({
chat ? chat->count : channel->membersCount(),
chat ? int(chat->participants.size()) : 0,
0,
});
return (chat && !chat->amIn())
? tr::lng_chat_status_unaccessible(tr::now)
: (count > 0)
? ((channel && channel->isBroadcast())
? tr::lng_chat_status_subscribers(
tr::now,
lt_count_decimal,
count)
: tr::lng_chat_status_members(
tr::now,
lt_count_decimal,
count))
: ((channel && channel->isBroadcast())
? tr::lng_channel_status(tr::now)
: tr::lng_group_status(tr::now));
});
}
void ValidatePhotoId(
not_null<UserpicState*> state,
PhotoId oldUserpicPhotoId) {
if (state->userSlice) {
const auto count = state->userSlice->size();
const auto hasOld = state->userSlice->indexOf(
oldUserpicPhotoId).has_value();
const auto hasNew = state->userSlice->indexOf(
state->userpicPhotoId).has_value();
const auto shift = (hasNew ? 0 : 1);
const auto fullCount = count + shift;
state->current.count = fullCount;
if (hasOld && !hasNew && state->current.index + 1 < fullCount) {
++state->current.index;
} else if (!hasOld && hasNew && state->current.index > 0) {
--state->current.index;
}
const auto index = state->current.index;
if (!index || index >= fullCount) {
state->current.index = 0;
state->photoId = state->userpicPhotoId;
} else {
state->photoId = (*state->userSlice)[index - shift];
}
} else {
state->photoId = state->userpicPhotoId;
}
}
bool ProcessCurrent(
not_null<PeerData*> peer,
not_null<UserpicState*> state) {
const auto userpicPhotoId = peer->userpicPhotoId();
const auto userpicPhoto = (userpicPhotoId
&& (userpicPhotoId != PeerData::kUnknownPhotoId)
&& (state->userpicPhotoId != userpicPhotoId))
? peer->owner().photo(userpicPhotoId).get()
: (state->photoId == userpicPhotoId && state->photoView)
? state->photoView->owner().get()
: nullptr;
state->waitingFull = (state->userpicPhotoId != userpicPhotoId)
&& ((userpicPhotoId == PeerData::kUnknownPhotoId)
|| (userpicPhotoId && userpicPhoto->isNull()));
if (state->waitingFull) {
peer->updateFullForced();
}
const auto oldUserpicPhotoId = state->waitingFull
? state->userpicPhotoId
: std::exchange(state->userpicPhotoId, userpicPhotoId);
const auto changedUserpic = (state->userpicKey
!= peer->userpicUniqueKey(state->userpicView));
const auto wasIndex = state->current.index;
const auto wasCount = state->current.count;
const auto wasPhotoId = state->photoId;
ValidatePhotoId(state, oldUserpicPhotoId);
const auto changedInSlice = (state->current.index != wasIndex)
|| (state->current.count != wasCount);
const auto changedPhotoId = (state->photoId != wasPhotoId);
const auto photo = (state->photoId == state->userpicPhotoId
&& userpicPhoto)
? userpicPhoto
: (state->photoId
&& (state->photoId != PeerData::kUnknownPhotoId)
&& changedPhotoId)
? peer->owner().photo(state->photoId).get()
: state->photoView
? state->photoView->owner().get()
: nullptr;
state->waitingLoad = false;
if (!changedPhotoId
&& (state->current.index > 0 || !changedUserpic)
&& !state->photoView
&& (!state->current.photo.isNull()
|| state->current.videoDocument)) {
return changedInSlice;
} else if (photo && !photo->isNull()) {
ProcessFullPhoto(peer, state, photo);
} else if (state->current.index > 0) {
return changedInSlice;
} else {
ProcessUserpic(peer, state);
}
return true;
}
[[nodiscard]] PreparedShortInfoUserpic UserpicValue(
not_null<PeerData*> peer) {
const auto moveRequests = std::make_shared<rpl::event_stream<int>>();
auto move = [=](int shift) {
moveRequests->fire_copy(shift);
};
auto value = [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto state = lifetime.make_state<UserpicState>();
const auto push = [=](bool force = false) {
if (ProcessCurrent(peer, state) || force) {
consumer.put_next_copy(state->current);
}
};
using UpdateFlag = Data::PeerUpdate::Flag;
peer->session().changes().peerFlagsValue(
peer,
UpdateFlag::Photo | UpdateFlag::FullInfo
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.flags & UpdateFlag::Photo) || state->waitingFull;
}) | rpl::start_with_next([=] {
push();
}, lifetime);
if (const auto user = peer->asUser()) {
UserPhotosReversedViewer(
&peer->session(),
UserPhotosSlice::Key(peerToUser(user->id), PhotoId()),
kOverviewLimit,
kOverviewLimit
) | rpl::start_with_next([=](UserPhotosSlice &&slice) {
state->userSlice = std::move(slice);
push();
}, lifetime);
}
moveRequests->events(
) | rpl::filter([=] {
return (state->current.count > 1);
}) | rpl::start_with_next([=](int shift) {
state->current.index = std::clamp(
((state->current.index + shift + state->current.count)
% state->current.count),
0,
state->current.count - 1);
push(true);
}, lifetime);
peer->session().downloaderTaskFinished(
) | rpl::filter([=] {
return state->waitingLoad
&& (state->photoView
? (!!state->photoView->image(Data::PhotoSize::Large))
: (state->userpicView && state->userpicView->image()));
}) | rpl::start_with_next([=] {
push();
}, lifetime);
return lifetime;
};
return { .value = std::move(value), .move = std::move(move) };
}
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused) {
const auto type = peer->isUser()
? PeerShortInfoType::User
: peer->isBroadcast()
? PeerShortInfoType::Channel
: PeerShortInfoType::Group;
auto userpic = UserpicValue(peer);
auto result = Box<PeerShortInfoBox>(
type,
FieldsValue(peer),
StatusValue(peer),
std::move(userpic.value),
std::move(videoPaused));
result->openRequests(
) | rpl::start_with_next(open, result->lifetime());
result->moveRequests(
) | rpl::start_with_next(userpic.move, result->lifetime());
return result;
}
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation) {
const auto open = [=] { navigation->showPeerHistory(peer); };
const auto videoIsPaused = [=] {
return navigation->parentController()->isGifPausedAtLeastFor(
Window::GifPauseReason::Layer);
};
return PrepareShortInfoBox(
peer,
open,
videoIsPaused);
}
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
return StatusValue(peer);
}
PreparedShortInfoUserpic PrepareShortInfoUserpic(not_null<PeerData*> peer) {
return UserpicValue(peer);
}

View File

@@ -0,0 +1,42 @@
/*
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
#include "base/object_ptr.h"
class PeerData;
namespace Ui {
class BoxContent;
} // namespace Ui
namespace Window {
class SessionNavigation;
} // namespace Window
struct PeerShortInfoUserpic;
struct PreparedShortInfoUserpic {
rpl::producer<PeerShortInfoUserpic> value;
Fn<void(int)> move;
};
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
Fn<void()> open,
Fn<bool()> videoPaused);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation);
[[nodiscard]] rpl::producer<QString> PrepareShortInfoStatus(
not_null<PeerData*> peer);
[[nodiscard]] PreparedShortInfoUserpic PrepareShortInfoUserpic(
not_null<PeerData*> peer);

View File

@@ -0,0 +1,68 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/phone_banned_box.h"
#include "ui/boxes/confirm_box.h"
#include "core/click_handler_types.h" // UrlClickHandler
#include "base/qthelp_url.h" // qthelp::url_encode
#include "base/platform/base_platform_info.h"
#include "window/window_controller.h"
#include "lang/lang_keys.h"
namespace Ui {
namespace {
void SendToBannedHelp(const QString &phone) {
const auto version = QString::fromLatin1(AppVersionStr)
+ (cAlphaVersion()
? qsl(" alpha %1").arg(cAlphaVersion())
: (AppBetaVersion ? " beta" : ""));
const auto subject = qsl("Banned phone number: ") + phone;
const auto body = qsl("\
I'm trying to use my mobile phone number: ") + phone + qsl("\n\
But Telegram says it's banned. Please help.\n\
\n\
App version: ") + version + qsl("\n\
OS version: ") + ::Platform::SystemVersionPretty() + qsl("\n\
Locale: ") + ::Platform::SystemLanguage();
const auto url = "mailto:?to="
+ qthelp::url_encode("login@stel.com")
+ "&subject="
+ qthelp::url_encode(subject)
+ "&body="
+ qthelp::url_encode(body);
UrlClickHandler::Open(url);
}
} // namespace
void ShowPhoneBannedError(
not_null<Window::Controller*> controller,
const QString &phone) {
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto close = [=] {
if (*box) {
(*box)->closeBox();
}
};
*box = controller->show(
Box<Ui::ConfirmBox>(
tr::lng_signin_banned_text(tr::now),
tr::lng_box_ok(tr::now),
tr::lng_signin_banned_help(tr::now),
close,
[=] { SendToBannedHelp(phone); close(); }),
Ui::LayerOption::CloseOther);
}
} // namespace Ui

View File

@@ -0,0 +1,20 @@
/*
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 Controller;
} // namespace Window
namespace Ui {
void ShowPhoneBannedError(
not_null<Window::Controller*> controller,
const QString &phone);
} // namespace Ui

View File

@@ -0,0 +1,129 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/pin_messages_box.h"
#include "apiwrap.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
namespace {
[[nodiscard]] bool IsOldForPin(MsgId id, not_null<PeerData*> peer) {
const auto normal = peer->migrateToOrMe();
const auto migrated = normal->migrateFrom();
const auto top = Data::ResolveTopPinnedId(normal, migrated);
if (!top) {
return false;
} else if (peer == migrated) {
return top.channel || (id < top.msg);
} else if (migrated) {
return top.channel && (id < top.msg);
} else {
return (id < top.msg);
}
}
} // namespace
PinMessageBox::PinMessageBox(
QWidget*,
not_null<PeerData*> peer,
MsgId msgId)
: _peer(peer)
, _api(&peer->session().mtp())
, _msgId(msgId)
, _pinningOld(IsOldForPin(msgId, peer))
, _text(
this,
(_pinningOld
? tr::lng_pinned_pin_old_sure(tr::now)
: (peer->isChat() || peer->isMegagroup())
? tr::lng_pinned_pin_sure_group(tr::now)
: tr::lng_pinned_pin_sure(tr::now)),
st::boxLabel) {
}
void PinMessageBox::prepare() {
addButton(tr::lng_pinned_pin(), [this] { pinMessage(); });
addButton(tr::lng_cancel(), [this] { closeBox(); });
if (_peer->isUser() && !_peer->isSelf()) {
_pinForPeer.create(
this,
tr::lng_pinned_also_for_other(
tr::now,
lt_user,
_peer->shortName()),
false,
st::defaultBoxCheckbox);
_checkbox = _pinForPeer;
} else if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) {
_notify.create(
this,
tr::lng_pinned_notify(tr::now),
true,
st::defaultBoxCheckbox);
_checkbox = _notify;
}
auto height = st::boxPadding.top()
+ _text->height()
+ st::boxPadding.bottom();
if (_checkbox) {
height += st::boxMediumSkip + _checkbox->heightNoMargins();
}
setDimensions(st::boxWidth, height);
}
void PinMessageBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
if (_checkbox) {
_checkbox->moveToLeft(
st::boxPadding.left(),
_text->y() + _text->height() + st::boxMediumSkip);
}
}
void PinMessageBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
pinMessage();
} else {
BoxContent::keyPressEvent(e);
}
}
void PinMessageBox::pinMessage() {
if (_requestId) {
return;
}
auto flags = MTPmessages_UpdatePinnedMessage::Flags(0);
if (_notify && !_notify->checked()) {
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_silent;
}
if (_pinForPeer && !_pinForPeer->checked()) {
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_pm_oneside;
}
_requestId = _api.request(MTPmessages_UpdatePinnedMessage(
MTP_flags(flags),
_peer->input,
MTP_int(_msgId)
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
Ui::hideLayer();
}).fail([=](const MTP::Error &error) {
Ui::hideLayer();
}).send();
}

View File

@@ -0,0 +1,43 @@
/*
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
#include "boxes/abstract_box.h"
#include "mtproto/sender.h"
namespace Ui {
class Checkbox;
class FlatLabel;
} // namespace Ui
class PinMessageBox final : public Ui::BoxContent {
public:
PinMessageBox(QWidget*, not_null<PeerData*> peer, MsgId msgId);
protected:
void prepare() override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void pinMessage();
const not_null<PeerData*> _peer;
MTP::Sender _api;
MsgId _msgId = 0;
bool _pinningOld = false;
object_ptr<Ui::FlatLabel> _text;
object_ptr<Ui::Checkbox> _notify = { nullptr };
object_ptr<Ui::Checkbox> _pinForPeer = { nullptr };
QPointer<Ui::Checkbox> _checkbox;
mtpRequestId _requestId = 0;
};

View File

@@ -1,143 +0,0 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/rate_call_box.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "apiwrap.h"
#include "styles/style_layers.h"
#include "styles/style_calls.h"
namespace {
constexpr auto kMaxRating = 5;
constexpr auto kRateCallCommentLengthMax = 200;
} // namespace
RateCallBox::RateCallBox(
QWidget*,
not_null<Main::Session*> session,
uint64 callId,
uint64 callAccessHash)
: _session(session)
, _api(&_session->mtp())
, _callId(callId)
, _callAccessHash(callAccessHash) {
}
void RateCallBox::prepare() {
setTitle(tr::lng_call_rate_label());
addButton(tr::lng_cancel(), [this] { closeBox(); });
for (auto i = 0; i < kMaxRating; ++i) {
_stars.emplace_back(this, st::callRatingStar);
_stars.back()->setClickedCallback([this, value = i + 1] { ratingChanged(value); });
_stars.back()->show();
}
updateMaxHeight();
}
void RateCallBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
auto starsWidth = (_stars.size() * st::callRatingStar.width);
auto starLeft = (width() - starsWidth) / 2;
auto starTop = st::callRatingStarTop;
for (auto &star : _stars) {
star->moveToLeft(starLeft, starTop);
starLeft += star->width();
}
if (_comment) {
_comment->moveToLeft(st::callRatingPadding.left(), _stars.back()->bottomNoMargins() + st::callRatingCommentTop);
}
}
void RateCallBox::ratingChanged(int value) {
Expects(value > 0 && value <= kMaxRating);
if (!_rating) {
clearButtons();
addButton(tr::lng_send_button(), [this] { send(); });
addButton(tr::lng_cancel(), [this] { closeBox(); });
}
_rating = value;
for (auto i = 0; i < kMaxRating; ++i) {
_stars[i]->setIconOverride((i < value) ? &st::callRatingStarFilled : nullptr);
_stars[i]->setRippleColorOverride((i < value) ? &st::lightButtonBgOver : nullptr);
}
if (value < kMaxRating) {
if (!_comment) {
_comment.create(
this,
st::callRatingComment,
Ui::InputField::Mode::MultiLine,
tr::lng_call_rate_comment());
_comment->show();
_comment->setSubmitSettings(Core::App().settings().sendSubmitWay());
_comment->setMaxLength(kRateCallCommentLengthMax);
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());
updateMaxHeight();
connect(_comment, &Ui::InputField::resized, [=] { commentResized(); });
connect(_comment, &Ui::InputField::submitted, [=] { send(); });
connect(_comment, &Ui::InputField::cancelled, [=] { closeBox(); });
}
_comment->setFocusFast();
} else if (_comment) {
_comment.destroy();
updateMaxHeight();
}
}
void RateCallBox::setInnerFocus() {
if (_comment) {
_comment->setFocusFast();
} else {
setFocus();
}
}
void RateCallBox::commentResized() {
updateMaxHeight();
update();
}
void RateCallBox::send() {
Expects(_rating > 0 && _rating <= kMaxRating);
if (_requestId) {
return;
}
auto comment = _comment ? _comment->getLastText().trimmed() : QString();
_requestId = _api.request(MTPphone_SetCallRating(
MTP_flags(0),
MTP_inputPhoneCall(MTP_long(_callId), MTP_long(_callAccessHash)),
MTP_int(_rating),
MTP_string(comment)
)).done([=](const MTPUpdates &updates) {
_session->api().applyUpdates(updates);
closeBox();
}).fail([=](const MTP::Error &error) { closeBox(); }).send();
}
void RateCallBox::updateMaxHeight() {
auto newHeight = st::callRatingPadding.top() + st::callRatingStarTop + _stars.back()->heightNoMargins() + st::callRatingPadding.bottom();
if (_comment) {
newHeight += st::callRatingCommentTop + _comment->height();
}
setDimensions(st::boxWideWidth, newHeight);
}

View File

@@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "confirm_box.h"
#include "editor/photo_editor_layer_widget.h"
#include "history/history_drag_area.h"
#include "history/view/history_view_schedule_box.h"

View File

@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "base/timer.h"
#include "base/unixtime.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "styles/style_boxes.h"
@@ -81,7 +81,7 @@ private:
Full _data;
object_ptr<Inner> _inner;
QPointer<ConfirmBox> _terminateBox;
QPointer<Ui::ConfirmBox> _terminateBox;
base::Timer _shortPollTimer;
@@ -250,7 +250,7 @@ void SessionsContent::terminate(Fn<void()> terminateRequest, QString message) {
terminateRequest();
});
_terminateBox = Ui::show(
Box<ConfirmBox>(
Box<Ui::ConfirmBox>(
message,
tr::lng_settings_reset_button(tr::now),
st::attentionBoxButton,

View File

@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "base/qthelp_url.h"
#include "storage/storage_account.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "apiwrap.h"
#include "ui/toast/toast.h"
#include "ui/widgets/multi_select.h"
@@ -72,7 +72,7 @@ protected:
int visibleBottom) override;
void paintEvent(QPaintEvent *e) override;
void enterEventHook(QEvent *e) override;
void enterEventHook(QEnterEvent *e) override;
void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
@@ -906,7 +906,7 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) {
}
}
void ShareBox::Inner::enterEventHook(QEvent *e) {
void ShareBox::Inner::enterEventHook(QEnterEvent *e) {
setMouseTracking(true);
}
@@ -1113,7 +1113,7 @@ QString AppendShareGameScoreUrl(
auto channelAccessHash = uint64(channel ? channel->access : 0);
shareHashDataInts[0] = session->userId().bare;
shareHashDataInts[1] = fullId.channel.bare;
shareHashDataInts[2] = fullId.msg;
shareHashDataInts[2] = uint64(fullId.msg.bare);
shareHashDataInts[3] = channelAccessHash;
// Count SHA1() of data.
@@ -1155,7 +1155,8 @@ void ShareGameScoreByHash(
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
Ui::show(Box<InformBox>(tr::lng_confirm_phone_link_invalid(tr::now)));
Ui::show(Box<Ui::InformBox>(
tr::lng_confirm_phone_link_invalid(tr::now)));
return;
}
@@ -1175,19 +1176,19 @@ void ShareGameScoreByHash(
//// Check next 64 bits of SHA1() of data.
//auto skipSha1Part = sizeof(channelAccessHash);
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
// Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
// return;
//}
// Check 128 bits of SHA1() of data.
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
return;
}
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
if (hashDataInts[0] != session->userId().bare) {
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
return;
}
@@ -1195,20 +1196,19 @@ void ShareGameScoreByHash(
auto channelAccessHash = hashDataInts[3];
//auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash);
//if (channelAccessHashInts[0] != hashDataInts[3]) {
// Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
// return;
//}
if (((hashDataInts[1] >> 40) != 0)
|| ((hashDataInts[2] >> 32) != 0)
|| (!hashDataInts[1] && channelAccessHash)) {
// If there is no channel id, there should be no channel access_hash.
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
return;
}
auto channelId = ChannelId(hashDataInts[1]);
auto msgId = MsgId(hashDataInts[2]);
auto msgId = MsgId(int64(hashDataInts[2]));
if (const auto item = session->data().message(channelId, msgId)) {
FastShareMessage(item);
} else {
@@ -1219,7 +1219,8 @@ void ShareGameScoreByHash(
if (const auto item = session->data().message(channel, msgId)) {
FastShareMessage(item);
} else {
Ui::show(Box<InformBox>(tr::lng_edit_deleted(tr::now)));
Ui::show(Box<Ui::InformBox>(
tr::lng_edit_deleted(tr::now)));
}
});
};

View File

@@ -12,12 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "data/stickers/data_stickers.h"
#include "chat_helpers/send_context_menu.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "core/application.h"
#include "mtproto/sender.h"
#include "storage/storage_account.h"
#include "dialogs/dialogs_layout.h"
#include "dialogs/ui/dialogs_layout.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/image/image.h"
@@ -35,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_toggling_media.h"
#include "api/api_common.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_layers.h"
@@ -87,6 +90,7 @@ protected:
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void leaveEventHook(QEvent *e) override;
@@ -113,6 +117,8 @@ private:
void gotSet(const MTPmessages_StickerSet &set);
void installDone(const MTPmessages_StickerSetInstallResult &result);
void send(not_null<DocumentData*> sticker, Api::SendOptions options);
not_null<Lottie::MultiPlayer*> getLottiePlayer();
void showPreview();
@@ -144,6 +150,8 @@ private:
base::Timer _previewTimer;
int _previewShown = -1;
base::unique_qptr<Ui::PopupMenu> _menu;
rpl::event_stream<uint64> _setInstalled;
rpl::event_stream<uint64> _setArchived;
rpl::event_stream<> _updateControls;
@@ -257,7 +265,7 @@ void StickerSetBox::handleError(Error error) {
switch (error) {
case Error::NotFound:
_controller->show(
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
break;
default: Unexpected("Error in StickerSetBox::handleError.");
}
@@ -404,7 +412,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
auto p = StickersPack();
p.reserve(stickers.size());
for (auto j = 0, c = stickers.size(); j != c; ++j) {
for (auto j = 0, c = int(stickers.size()); j != c; ++j) {
auto doc = _controller->session().data().document(stickers[j].v);
if (!doc || !doc->sticker()) continue;
@@ -532,7 +540,7 @@ void StickerSetBox::Inner::installDone(
auto &order = isMasks
? stickers.maskSetsOrderRef()
: stickers.setsOrderRef();
const auto insertAtIndex = 0, currentIndex = order.indexOf(_setId);
const auto insertAtIndex = 0, currentIndex = int(order.indexOf(_setId));
if (currentIndex != insertAtIndex) {
if (currentIndex > 0) {
order.removeAt(currentIndex);
@@ -577,10 +585,14 @@ void StickerSetBox::Inner::installDone(
}
void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
int index = stickerFromGlobalPos(e->globalPos());
if (index >= 0 && index < _pack.size()) {
_previewTimer.callOnce(QApplication::startDragTime());
if (e->button() != Qt::LeftButton) {
return;
}
const auto index = stickerFromGlobalPos(e->globalPos());
if (index < 0 || index >= _pack.size()) {
return;
}
_previewTimer.callOnce(QApplication::startDragTime());
}
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
@@ -605,18 +617,62 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_previewShown = -1;
return;
}
if (_previewTimer.isActive()) {
_previewTimer.cancel();
const auto index = stickerFromGlobalPos(e->globalPos());
if (index >= 0 && index < _pack.size() && !isMasksSet()) {
const auto sticker = _pack[index];
Ui::PostponeCall(crl::guard(_controller, [=] {
if (_controller->content()->sendExistingDocument(sticker)) {
Ui::hideSettingsAndLayer();
}
}));
}
if (!_previewTimer.isActive()) {
return;
}
_previewTimer.cancel();
const auto index = stickerFromGlobalPos(e->globalPos());
if (index < 0 || index >= _pack.size() || isMasksSet()) {
return;
}
send(_pack[index], Api::SendOptions());
}
void StickerSetBox::Inner::send(
not_null<DocumentData*> sticker,
Api::SendOptions options) {
const auto controller = _controller;
Ui::PostponeCall(controller, [=] {
if (controller->content()->sendExistingDocument(sticker, options)) {
Ui::hideSettingsAndLayer();
}
});
}
void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
const auto index = stickerFromGlobalPos(e->globalPos());
if (index < 0 || index >= _pack.size()) {
return;
}
const auto type = _controller->content()->sendMenuType();
if (type == SendMenu::Type::Disabled) {
return;
}
_previewTimer.cancel();
_menu = base::make_unique_q<Ui::PopupMenu>(this);
const auto document = _pack[index];
const auto sendSelected = [=](Api::SendOptions options) {
send(document, options);
};
SendMenu::FillSendMenu(
_menu.get(),
type,
SendMenu::DefaultSilentCallback(sendSelected),
SendMenu::DefaultScheduleCallback(this, type, sendSelected));
const auto toggleFavedSticker = [=] {
Api::ToggleFavedSticker(
document,
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
};
_menu->addAction(
(document->owner().stickers().isFaved(document)
? tr::lng_faved_stickers_remove
: tr::lng_faved_stickers_add)(tr::now),
toggleFavedSticker);
_menu->popup(QCursor::pos());
}
void StickerSetBox::Inner::updateSelected() {

View File

@@ -11,13 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "data/stickers/data_stickers.h"
class ConfirmBox;
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
class ConfirmBox;
class PlainShadow;
} // namespace Ui

View File

@@ -17,11 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "boxes/confirm_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/sticker_set_box.h"
#include "apiwrap.h"
#include "storage/storage_account.h"
#include "dialogs/dialogs_layout.h"
#include "dialogs/ui/dialogs_layout.h"
#include "lottie/lottie_single_player.h"
#include "chat_helpers/stickers_lottie.h"
#include "ui/widgets/buttons.h"
@@ -64,7 +64,7 @@ private:
void setCounter(int counter);
QString _text;
Dialogs::Layout::UnreadBadgeStyle _st;
Dialogs::Ui::UnreadBadgeStyle _st;
};
@@ -303,7 +303,7 @@ StickersBox::CounterWidget::CounterWidget(
: RpWidget(parent) {
setAttribute(Qt::WA_TransparentForMouseEvents);
_st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
_st.sizeId = Dialogs::Ui::UnreadBadgeInStickersBox;
_st.textTop = st::stickersFeaturedBadgeTextTop;
_st.size = st::stickersFeaturedBadgeSize;
_st.padding = st::stickersFeaturedBadgePadding;
@@ -323,7 +323,7 @@ void StickersBox::CounterWidget::setCounter(int counter) {
Painter p(&dummy);
auto newWidth = 0;
Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
resize(newWidth, st::stickersFeaturedBadgeSize);
}
@@ -334,7 +334,7 @@ void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
if (!_text.isEmpty()) {
auto unreadRight = rtl() ? 0 : width();
auto unreadTop = 0;
Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
}
}

View File

@@ -14,13 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h"
#include "ui/special_fields.h"
class ConfirmBox;
namespace style {
struct RippleAnimation;
} // namespace style
namespace Ui {
class ConfirmBox;
class PlainShadow;
class RippleAnimation;
class SettingsSlider;

View File

@@ -7,13 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/username_box.h"
#include "boxes/peers/edit_peer_common.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "ui/widgets/buttons.h"
#include "ui/special_fields.h"
#include "ui/toast/toast.h"
#include "core/application.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
@@ -23,14 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
namespace {
constexpr auto kMinUsernameLength = 5;
} // namespace
UsernameBox::UsernameBox(QWidget*, not_null<Main::Session*> session)
: _session(session)
, _textStyle(st::usernameTextStyle)
, _font(st::boxTextFont)
, _padding(st::usernamePadding)
, _textCenterTop((_textStyle.lineHeight - _font->height) / 2)
, _api(&_session->mtp())
, _username(
this,
@@ -39,8 +35,8 @@ UsernameBox::UsernameBox(QWidget*, not_null<Main::Session*> session)
session->user()->username,
QString())
, _link(this, QString(), st::boxLinkButton)
, _about(st::boxWidth - st::usernamePadding.left())
, _checkTimer(this) {
, _about(st::boxWidth - _padding.left())
, _checkTimer([=] { check(); }) {
}
void UsernameBox::prepare() {
@@ -57,11 +53,15 @@ void UsernameBox::prepare() {
connect(_username, &Ui::MaskedInputField::submitted, [=] { save(); });
_link->addClickHandler([=] { linkClick(); });
_about.setText(st::usernameTextStyle, tr::lng_username_about(tr::now));
setDimensions(st::boxWidth, st::usernamePadding.top() + _username->height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom());
_checkTimer->setSingleShot(true);
connect(_checkTimer, &QTimer::timeout, [=] { check(); });
_about.setText(_textStyle, tr::lng_username_about(tr::now));
setDimensions(
st::boxWidth,
_padding.top()
+ _username->height()
+ st::usernameSkip
+ _about.countHeight(st::boxWidth - _padding.left())
+ 3 * _textStyle.lineHeight
+ _padding.bottom());
updateLinkText();
}
@@ -75,106 +75,178 @@ void UsernameBox::paintEvent(QPaintEvent *e) {
Painter p(this);
p.setFont(st::boxTextFont);
const auto textTop = _username->y()
+ _username->height()
+ ((st::usernameSkip - _font->height) / 2);
p.setFont(_font);
if (!_errorText.isEmpty()) {
p.setPen(st::boxTextFgError);
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText);
p.drawTextLeft(
_padding.left(),
textTop,
width(),
_errorText);
} else if (!_goodText.isEmpty()) {
p.setPen(st::boxTextFgGood);
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText);
p.drawTextLeft(
_padding.left(),
textTop,
width(),
_goodText);
} else {
p.setPen(st::usernameDefaultFg);
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), tr::lng_username_choose(tr::now));
p.drawTextLeft(
_padding.left(),
textTop,
width(),
tr::lng_username_choose(tr::now));
}
p.setPen(st::boxTextFg);
int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw);
_about.drawLeft(p, st::usernamePadding.left(), _username->y() + _username->height() + st::usernameSkip, availw, width());
const auto availW = st::boxWidth - _padding.left();
const auto h = _about.countHeight(availW);
_about.drawLeft(
p,
_padding.left(),
_username->y() + _username->height() + st::usernameSkip,
availW,
width());
int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2);
const auto linkTop = _username->y()
+ _username->height()
+ st::usernameSkip
+ h
+ _textStyle.lineHeight
+ _textCenterTop;
if (_link->isHidden()) {
p.drawTextLeft(st::usernamePadding.left(), linky, width(), tr::lng_username_link_willbe(tr::now));
p.drawTextLeft(
_padding.left(),
linkTop,
width(),
tr::lng_username_link_willbe(tr::now));
p.setPen(st::usernameDefaultFg);
const auto link = _session->createInternalLinkFull(qsl("username"));
p.drawTextLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2), width(), link);
p.drawTextLeft(
_padding.left(),
linkTop
+ _textStyle.lineHeight
+ _textCenterTop,
width(),
link);
} else {
p.drawTextLeft(st::usernamePadding.left(), linky, width(), tr::lng_username_link(tr::now));
p.drawTextLeft(
_padding.left(),
linkTop,
width(),
tr::lng_username_link(tr::now));
}
}
void UsernameBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_username->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _username->height());
_username->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top());
_username->resize(
width() - _padding.left() - _padding.right(),
_username->height());
_username->moveToLeft(_padding.left(), _padding.top());
int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw);
int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2);
_link->moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2));
const auto availW = st::boxWidth - _padding.left();
const auto h = _about.countHeight(availW);
const auto linkTop = _username->y()
+ _username->height()
+ st::usernameSkip
+ h
+ _textStyle.lineHeight
+ _textCenterTop;
_link->moveToLeft(
_padding.left(),
linkTop + _textStyle.lineHeight + _textCenterTop);
}
void UsernameBox::save() {
if (_saveRequestId) return;
if (_saveRequestId) {
return;
}
_sentUsername = getName();
_saveRequestId = _api.request(MTPaccount_UpdateUsername(
MTP_string(_sentUsername)
)).done([=](const MTPUser &result) {
updateDone(result);
_saveRequestId = 0;
_session->data().processUser(result);
closeBox();
}).fail([=](const MTP::Error &error) {
updateFail(error);
_saveRequestId = 0;
updateFail(error.type());
}).send();
}
void UsernameBox::check() {
_api.request(base::take(_checkRequestId)).cancel();
QString name = getName();
if (name.size() >= kMinUsernameLength) {
_checkUsername = name;
_checkRequestId = _api.request(MTPaccount_CheckUsername(
MTP_string(name)
)).done([=](const MTPBool &result) {
checkDone(result);
}).fail([=](const MTP::Error &error) {
checkFail(error);
}).send();
const auto name = getName();
if (name.size() < Ui::EditPeer::kMinUsernameLength) {
return;
}
_checkUsername = name;
_checkRequestId = _api.request(MTPaccount_CheckUsername(
MTP_string(name)
)).done([=](const MTPBool &result) {
_checkRequestId = 0;
_errorText = (mtpIsTrue(result)
|| _checkUsername == _session->user()->username)
? QString()
: tr::lng_username_occupied(tr::now);
_goodText = _errorText.isEmpty()
? tr::lng_username_available(tr::now)
: QString();
update();
}).fail([=](const MTP::Error &error) {
_checkRequestId = 0;
checkFail(error.type());
}).send();
}
void UsernameBox::changed() {
updateLinkText();
QString name = getName();
const auto name = getName();
if (name.isEmpty()) {
if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
_errorText = _goodText = QString();
update();
}
_checkTimer->stop();
_checkTimer.cancel();
} else {
int32 len = name.size();
for (int32 i = 0; i < len; ++i) {
QChar ch = name.at(i);
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) {
const auto len = int(name.size());
for (auto i = 0; i < len; ++i) {
const auto ch = name.at(i);
if ((ch < 'A' || ch > 'Z')
&& (ch < 'a' || ch > 'z')
&& (ch < '0' || ch > '9')
&& ch != '_'
&& (ch != '@' || i > 0)) {
if (_errorText != tr::lng_username_bad_symbols(tr::now)) {
_errorText = tr::lng_username_bad_symbols(tr::now);
update();
}
_checkTimer->stop();
_checkTimer.cancel();
return;
}
}
if (name.size() < kMinUsernameLength) {
if (name.size() < Ui::EditPeer::kMinUsernameLength) {
if (_errorText != tr::lng_username_too_short(tr::now)) {
_errorText = tr::lng_username_too_short(tr::now);
update();
}
_checkTimer->stop();
_checkTimer.cancel();
} else {
if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
_errorText = _goodText = QString();
update();
}
_checkTimer->start(UsernameCheckTimeout);
_checkTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
}
}
}
@@ -185,28 +257,23 @@ void UsernameBox::linkClick() {
Ui::Toast::Show(tr::lng_username_copied(tr::now));
}
void UsernameBox::updateDone(const MTPUser &user) {
_session->data().processUser(user);
closeBox();
}
void UsernameBox::updateFail(const MTP::Error &error) {
_saveRequestId = 0;
void UsernameBox::updateFail(const QString &error) {
const auto self = _session->user();
const auto &err = error.type();
if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == self->username) {
if ((error == qstr("USERNAME_NOT_MODIFIED"))
|| (_sentUsername == self->username)) {
self->setName(
TextUtilities::SingleLine(self->firstName),
TextUtilities::SingleLine(self->lastName),
TextUtilities::SingleLine(self->nameOrPhone),
TextUtilities::SingleLine(_sentUsername));
closeBox();
} else if (err == qstr("USERNAME_INVALID")) {
} else if (error == qstr("USERNAME_INVALID")) {
_username->setFocus();
_username->showError();
_errorText = tr::lng_username_invalid(tr::now);
update();
} else if (err == qstr("USERNAME_OCCUPIED") || err == qstr("USERNAMES_UNAVAILABLE")) {
} else if ((error == qstr("USERNAME_OCCUPIED"))
|| (error == qstr("USERNAMES_UNAVAILABLE"))) {
_username->setFocus();
_username->showError();
_errorText = tr::lng_username_occupied(tr::now);
@@ -216,29 +283,12 @@ void UsernameBox::updateFail(const MTP::Error &error) {
}
}
void UsernameBox::checkDone(const MTPBool &result) {
_checkRequestId = 0;
const auto newError = (mtpIsTrue(result)
|| _checkUsername == _session->user()->username)
? QString()
: tr::lng_username_occupied(tr::now);
const auto newGood = newError.isEmpty()
? tr::lng_username_available(tr::now)
: QString();
if (_errorText != newError || _goodText != newGood) {
_errorText = newError;
_goodText = newGood;
update();
}
}
void UsernameBox::checkFail(const MTP::Error &error) {
_checkRequestId = 0;
QString err(error.type());
if (err == qstr("USERNAME_INVALID")) {
void UsernameBox::checkFail(const QString &error) {
if (error == qstr("USERNAME_INVALID")) {
_errorText = tr::lng_username_invalid(tr::now);
update();
} else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != _session->user()->username) {
} else if ((error == qstr("USERNAME_OCCUPIED"))
&& (_checkUsername != _session->user()->username)) {
_errorText = tr::lng_username_occupied(tr::now);
update();
} else {
@@ -252,8 +302,10 @@ QString UsernameBox::getName() const {
}
void UsernameBox::updateLinkText() {
QString uname = getName();
_link->setText(st::boxTextFont->elided(_session->createInternalLinkFull(uname), st::boxWidth - st::usernamePadding.left() - st::usernamePadding.right()));
const auto uname = getName();
_link->setText(_font->elided(
_session->createInternalLinkFull(uname),
st::boxWidth - _padding.left() - _padding.right()));
if (uname.isEmpty()) {
if (!_link->isHidden()) {
_link->hide();
@@ -265,4 +317,4 @@ void UsernameBox::updateLinkText() {
update();
}
}
}
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/abstract_box.h"
#include "base/timer.h"
#include "mtproto/sender.h"
namespace Ui {
@@ -31,11 +32,9 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
void updateDone(const MTPUser &result);
void updateFail(const MTP::Error &error);
void updateFail(const QString &error);
void checkDone(const MTPBool &result);
void checkFail(const MTP::Error &error);
void checkFail(const QString &error);
void save();
@@ -48,6 +47,10 @@ private:
void updateLinkText();
const not_null<Main::Session*> _session;
const style::TextStyle &_textStyle;
const style::font &_font;
const style::margins &_padding;
const int _textCenterTop;
MTP::Sender _api;
object_ptr<Ui::UsernameInput> _username;
@@ -58,6 +61,6 @@ private:
QString _sentUsername, _checkUsername, _errorText, _goodText;
Ui::Text::String _about;
object_ptr<QTimer> _checkTimer;
base::Timer _checkTimer;
};

View File

@@ -10,6 +10,7 @@ using "ui/basic.style";
using "ui/widgets/widgets.style";
using "ui/layers/layers.style";
using "ui/chat/chat.style"; // GroupCallUserpics
using "info/info.style"; // ShortInfoCover
using "window/window.style";
CallSignalBars {
@@ -542,6 +543,32 @@ groupCallPopupVolumeMenu: Menu(groupCallMenu) {
widthMin: 210px;
itemBgOver: groupCallMenuBg;
}
groupCallMenuCoverSize: 240px;
groupCallPopupCoverMenu: Menu(groupCallMenu) {
widthMin: groupCallMenuCoverSize;
widthMax: groupCallMenuCoverSize;
itemBgOver: groupCallMenuBg;
}
groupCallPopupMenuWithCover: PopupMenu(groupCallPopupMenu) {
scrollPadding: margins(0px, 0px, 0px, 8px);
menu: Menu(groupCallMenu) {
widthMin: groupCallMenuCoverSize;
widthMax: groupCallMenuCoverSize;
}
}
groupCallMenuCover: ShortInfoCover(shortInfoCover) {
size: groupCallMenuCoverSize;
namePosition: point(17px, 28px);
statusPosition: point(17px, 8px);
}
groupCallMenuAbout: FlatLabel(defaultFlatLabel) {
textFg: groupCallMemberNotJoinedStatus;
palette: TextPalette(defaultTextPalette) {
linkFg: groupCallActiveFg;
}
minWidth: 200px;
maxHeight: 92px;
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px);

View File

@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_media_types.h"
#include "data/data_user.h"
#include "boxes/confirm_box.h"
#include "boxes/delete_messages_box.h"
#include "base/unixtime.h"
#include "api/api_updates.h"
#include "apiwrap.h"
@@ -102,23 +102,25 @@ public:
int availableWidth,
int outerWidth,
bool selected) override;
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
void stopLastActionRipple() override;
void rightActionAddRipple(
QPoint point,
Fn<void()> updateCallback) override;
void rightActionStopLastRipple() override;
int nameIconWidth() const override {
return 0;
}
QSize actionSize() const override {
QSize rightActionSize() const override {
return peer()->isUser() ? QSize(_st->width, _st->height) : QSize();
}
QMargins actionMargins() const override {
QMargins rightActionMargins() const override {
return QMargins(
0,
0,
st::defaultPeerListItem.photoPosition.x(),
0);
}
void paintAction(
void rightActionPaint(
Painter &p,
int x,
int y,
@@ -141,7 +143,7 @@ private:
};
BoxController::Row::Row(not_null<HistoryItem*> item)
: PeerListRow(item->history()->peer, item->id)
: PeerListRow(item->history()->peer, item->id.bare)
, _items(1, item)
, _date(ItemDateTime(item).date())
, _type(ComputeType(item))
@@ -168,14 +170,14 @@ void BoxController::Row::paintStatusText(Painter &p, const style::PeerListItem &
PeerListRow::paintStatusText(p, st, x, y, availableWidth, outerWidth, selected);
}
void BoxController::Row::paintAction(
void BoxController::Row::rightActionPaint(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) {
auto size = actionSize();
auto size = rightActionSize();
if (_actionRipple) {
_actionRipple->paint(
p,
@@ -243,7 +245,7 @@ BoxController::Row::CallType BoxController::Row::ComputeCallType(
return CallType::Voice;
}
void BoxController::Row::addActionRipple(QPoint point, Fn<void()> updateCallback) {
void BoxController::Row::rightActionAddRipple(QPoint point, Fn<void()> updateCallback) {
if (!_actionRipple) {
auto mask = Ui::RippleAnimation::ellipseMask(
QSize(_st->rippleAreaSize, _st->rippleAreaSize));
@@ -255,7 +257,7 @@ void BoxController::Row::addActionRipple(QPoint point, Fn<void()> updateCallback
_actionRipple->add(point - _st->rippleAreaPosition);
}
void BoxController::Row::stopLastActionRipple() {
void BoxController::Row::rightActionStopLastRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
@@ -379,7 +381,7 @@ void BoxController::rowClicked(not_null<PeerListRow*> row) {
});
}
void BoxController::rowActionClicked(not_null<PeerListRow*> row) {
void BoxController::rowRightActionClicked(not_null<PeerListRow*> row) {
auto user = row->peer()->asUser();
Assert(user != nullptr);

View File

@@ -23,7 +23,7 @@ public:
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(

View File

@@ -12,8 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_app_config.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/rate_call_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/rate_call_box.h"
#include "calls/calls_instance.h"
#include "base/openssl_help.h"
#include "base/random.h"
@@ -47,6 +47,7 @@ namespace {
constexpr auto kMinLayer = 65;
constexpr auto kHangupTimeoutMs = 5000;
constexpr auto kSha256Size = 32;
constexpr auto kAuthKeySize = 256;
const auto kDefaultVersion = "2.4.4"_q;
const auto RegisterTag = tgcalls::Register<tgcalls::InstanceImpl>();
@@ -133,12 +134,11 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
[[nodiscard]] QVector<MTPstring> WrapVersions(
const std::vector<std::string> &data) {
auto result = QVector<MTPstring>();
result.reserve(data.size());
for (const auto &version : data) {
result.push_back(MTP_string(version));
}
return result;
return ranges::views::all(
data
) | ranges::views::transform([=](const std::string &string) {
return MTP_string(string);
}) | ranges::to<QVector<MTPstring>>;
}
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
@@ -172,6 +172,8 @@ Call::Call(
if (_type == Type::Outgoing) {
setState(State::Requesting);
} else {
const auto &config = _user->session().serverConfig();
_discardByTimeoutTimer.callOnce(config.callRingTimeoutMs);
startWaitingTrack();
}
setupOutgoingVideo();
@@ -247,16 +249,17 @@ void Call::startOutgoing() {
setState(State::Waiting);
auto &call = result.c_phone_phoneCall();
const auto &call = result.c_phone_phoneCall();
_user->session().data().processUsers(call.vusers());
if (call.vphone_call().type() != mtpc_phoneCallWaiting) {
LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()"));
LOG(("Call Error: Expected phoneCallWaiting in response to "
"phone.requestCall()"));
finish(FinishType::Failed);
return;
}
auto &phoneCall = call.vphone_call();
auto &waitingCall = phoneCall.c_phoneCallWaiting();
const auto &phoneCall = call.vphone_call();
const auto &waitingCall = phoneCall.c_phoneCallWaiting();
_id = waitingCall.vid().v;
_accessHash = waitingCall.vaccess_hash().v;
if (_finishAfterRequestingCall != FinishType::None) {
@@ -272,7 +275,7 @@ void Call::startOutgoing() {
_discardByTimeoutTimer.callOnce(config.callReceiveTimeoutMs);
handleUpdate(phoneCall);
}).fail([this](const MTP::Error &error) {
handleRequestError(error);
handleRequestError(error.type());
}).send();
}
@@ -287,7 +290,7 @@ void Call::startIncoming() {
setState(State::WaitingIncoming);
}
}).fail([=](const MTP::Error &error) {
handleRequestError(error);
handleRequestError(error.type());
}).send();
}
@@ -327,7 +330,7 @@ void Call::actuallyAnswer() {
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
const auto &call = result.c_phone_phoneCall();
_user->session().data().processUsers(call.vusers());
if (call.vphone_call().type() != mtpc_phoneCallWaiting) {
LOG(("Call Error: "
@@ -338,7 +341,7 @@ void Call::actuallyAnswer() {
handleUpdate(call.vphone_call());
}).fail([=](const MTP::Error &error) {
handleRequestError(error);
handleRequestError(error.type());
}).send();
}
@@ -410,10 +413,14 @@ void Call::hangup() {
if (state == State::Busy) {
_delegate->callFinished(this);
} else {
auto missed = (state == State::Ringing || (state == State::Waiting && _type == Type::Outgoing));
auto declined = isIncomingWaiting();
auto reason = missed ? MTP_phoneCallDiscardReasonMissed() :
declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup();
const auto missed = (state == State::Ringing
|| (state == State::Waiting && _type == Type::Outgoing));
const auto declined = isIncomingWaiting();
const auto reason = missed
? MTP_phoneCallDiscardReasonMissed()
: declined
? MTP_phoneCallDiscardReasonBusy()
: MTP_phoneCallDiscardReasonHangup();
finish(FinishType::Ended, reason);
}
}
@@ -438,7 +445,7 @@ QString Call::getDebugLog() const {
void Call::startWaitingTrack() {
_waitingTrack = Media::Audio::Current().createTrack();
auto trackFileName = Core::App().settings().getSoundPath(
const auto trackFileName = Core::App().settings().getSoundPath(
(_type == Type::Outgoing)
? qsl("call_outgoing")
: qsl("call_incoming"));
@@ -458,13 +465,13 @@ void Call::sendSignalingData(const QByteArray &data) {
finish(FinishType::Failed);
}
}).fail([=](const MTP::Error &error) {
handleRequestError(error);
handleRequestError(error.type());
}).send();
}
float64 Call::getWaitingSoundPeakValue() const {
if (_waitingTrack) {
auto when = crl::now() + kSoundSampleMs / 4;
const auto when = crl::now() + kSoundSampleMs / 4;
return _waitingTrack->getPeakValue(when);
}
return 0.;
@@ -478,20 +485,29 @@ bytes::vector Call::getKeyShaForFingerprint() const {
Expects(isKeyShaForFingerprintReady());
Expects(!_ga.empty());
auto encryptedChatAuthKey = bytes::vector(_authKey.size() + _ga.size(), gsl::byte {});
bytes::copy(gsl::make_span(encryptedChatAuthKey).subspan(0, _authKey.size()), _authKey);
bytes::copy(gsl::make_span(encryptedChatAuthKey).subspan(_authKey.size(), _ga.size()), _ga);
auto encryptedChatAuthKey = bytes::vector(
_authKey.size() + _ga.size(),
gsl::byte{});
bytes::copy(
gsl::make_span(encryptedChatAuthKey).subspan(0, _authKey.size()),
_authKey);
bytes::copy(
gsl::make_span(encryptedChatAuthKey).subspan(
_authKey.size(),
_ga.size()),
_ga);
return openssl::Sha256(encryptedChatAuthKey);
}
bool Call::handleUpdate(const MTPPhoneCall &call) {
switch (call.type()) {
case mtpc_phoneCallRequested: {
auto &data = call.c_phoneCallRequested();
const auto &data = call.c_phoneCallRequested();
if (_type != Type::Incoming
|| _id != 0
|| peerToUser(_user->id) != UserId(data.vadmin_id())) {
Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
Unexpected("phoneCallRequested call inside an existing call "
"handleUpdate()");
}
if (_user->session().userId() != UserId(data.vparticipant_id())) {
LOG(("Call Error: Wrong call participant_id %1, expected %2."
@@ -503,7 +519,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
_id = data.vid().v;
_accessHash = data.vaccess_hash().v;
auto gaHashBytes = bytes::make_span(data.vg_a_hash().v);
const auto gaHashBytes = bytes::make_span(data.vg_a_hash().v);
if (gaHashBytes.size() != kSha256Size) {
LOG(("Call Error: Wrong g_a_hash size %1, expected %2."
).arg(gaHashBytes.size()
@@ -515,7 +531,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} return true;
case mtpc_phoneCallEmpty: {
auto &data = call.c_phoneCallEmpty();
const auto &data = call.c_phoneCallEmpty();
if (data.vid().v != _id) {
return false;
}
@@ -524,7 +540,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} return true;
case mtpc_phoneCallWaiting: {
auto &data = call.c_phoneCallWaiting();
const auto &data = call.c_phoneCallWaiting();
if (data.vid().v != _id) {
return false;
}
@@ -539,7 +555,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} return true;
case mtpc_phoneCall: {
auto &data = call.c_phoneCall();
const auto &data = call.c_phoneCall();
if (data.vid().v != _id) {
return false;
}
@@ -551,12 +567,12 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} return true;
case mtpc_phoneCallDiscarded: {
auto &data = call.c_phoneCallDiscarded();
const auto &data = call.c_phoneCallDiscarded();
if (data.vid().v != _id) {
return false;
}
if (data.is_need_debug()) {
auto debugLog = _instance
const auto debugLog = _instance
? _instance->getDebugInfo()
: std::string();
if (!debugLog.empty()) {
@@ -569,10 +585,35 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
}
}
if (data.is_need_rating() && _id && _accessHash) {
Ui::show(Box<RateCallBox>(&_user->session(), _id, _accessHash));
const auto session = &_user->session();
const auto callId = _id;
const auto callAccessHash = _accessHash;
const auto box = Ui::show(Box<Ui::RateCallBox>(
Core::App().settings().sendSubmitWay()));
const auto sender = box->lifetime().make_state<MTP::Sender>(
&session->mtp());
box->sends(
) | rpl::take(
1 // Instead of keeping requestId.
) | rpl::start_with_next([=](const Ui::RateCallBox::Result &r) {
sender->request(MTPphone_SetCallRating(
MTP_flags(0),
MTP_inputPhoneCall(
MTP_long(callId),
MTP_long(callAccessHash)),
MTP_int(r.rating),
MTP_string(r.comment)
)).done([=](const MTPUpdates &updates) {
session->api().applyUpdates(updates);
box->closeBox();
}).fail([=] {
box->closeBox();
}).send();
}, box->lifetime());
}
const auto reason = data.vreason();
if (reason && reason->type() == mtpc_phoneCallDiscardReasonDisconnect) {
if (reason
&& reason->type() == mtpc_phoneCallDiscardReasonDisconnect) {
LOG(("Call Info: Discarded with DISCONNECT reason."));
}
if (reason && reason->type() == mtpc_phoneCallDiscardReasonBusy) {
@@ -586,7 +627,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
} return true;
case mtpc_phoneCallAccepted: {
auto &data = call.c_phoneCallAccepted();
const auto &data = call.c_phoneCallAccepted();
if (data.vid().v != _id) {
return false;
}
@@ -678,24 +719,25 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
)).done([=](const MTPphone_PhoneCall &result) {
Expects(result.type() == mtpc_phone_phoneCall);
auto &call = result.c_phone_phoneCall();
const auto &call = result.c_phone_phoneCall();
_user->session().data().processUsers(call.vusers());
if (call.vphone_call().type() != mtpc_phoneCall) {
LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()"));
LOG(("Call Error: Expected phoneCall in response to "
"phone.confirmCall()"));
finish(FinishType::Failed);
return;
}
createAndStartController(call.vphone_call().c_phoneCall());
}).fail([=](const MTP::Error &error) {
handleRequestError(error);
handleRequestError(error.type());
}).send();
}
void Call::startConfirmedCall(const MTPDphoneCall &call) {
Expects(_type == Type::Incoming);
auto firstBytes = bytes::make_span(call.vg_a_or_b().v);
const auto firstBytes = bytes::make_span(call.vg_a_or_b().v);
if (_gaHash != openssl::Sha256(firstBytes)) {
LOG(("Call Error: Wrong g_a hash received."));
finish(FinishType::Failed);
@@ -703,7 +745,10 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
}
_ga = bytes::vector(firstBytes.begin(), firstBytes.end());
auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
const auto computedAuthKey = MTP::CreateAuthKey(
firstBytes,
_randomPower,
_dhConfig.p);
if (computedAuthKey.empty()) {
LOG(("Call Error: Could not compute mod-exp final."));
finish(FinishType::Failed);
@@ -718,22 +763,25 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
void Call::createAndStartController(const MTPDphoneCall &call) {
_discardByTimeoutTimer.cancel();
if (!checkCallFields(call) || _authKey.size() != 256) {
if (!checkCallFields(call) || _authKey.size() != kAuthKeySize) {
return;
}
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
const auto &serverConfig = _user->session().serverConfig();
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>();
memcpy(encryptionKeyValue->data(), _authKey.data(), 256);
auto encryptionKeyValue = std::make_shared<std::array<
uint8_t,
kAuthKeySize>>();
memcpy(encryptionKeyValue->data(), _authKey.data(), kAuthKeySize);
const auto &settings = Core::App().settings();
const auto weak = base::make_weak(this);
tgcalls::Descriptor descriptor = {
.config = tgcalls::Config{
.initializationTimeout = serverConfig.callConnectTimeoutMs / 1000.,
.initializationTimeout =
serverConfig.callConnectTimeoutMs / 1000.,
.receiveTimeout = serverConfig.callPacketTimeoutMs / 1000.,
.dataSaving = tgcalls::DataSaving::Never,
.enableP2P = call.is_p2p_allowed(),
@@ -763,7 +811,9 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
handleControllerBarCountChange(count);
});
},
.remoteMediaStateUpdated = [=](tgcalls::AudioState audio, tgcalls::VideoState video) {
.remoteMediaStateUpdated = [=](
tgcalls::AudioState audio,
tgcalls::VideoState video) {
crl::on_main(weak, [=] {
updateRemoteMediaState(audio, video);
});
@@ -780,9 +830,9 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
settings.callAudioBackend()),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
auto callLogNative = QDir::toNativeSeparators(callLogPath);
const auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
const auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
const auto callLogNative = QDir::toNativeSeparators(callLogPath);
#ifdef Q_OS_WIN
descriptor.config.logPath.data = callLogNative.toStdWString();
#else // Q_OS_WIN
@@ -802,7 +852,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}
{
auto &settingsProxy = Core::App().settings().proxy();
const auto &settingsProxy = Core::App().settings().proxy();
using ProxyData = MTP::ProxyData;
if (settingsProxy.useProxyForCalls() && settingsProxy.isEnabled()) {
const auto &selected = settingsProxy.selected();
@@ -862,7 +912,7 @@ void Call::handleControllerStateChange(tgcalls::State state) {
} break;
case tgcalls::State::Failed: {
auto error = _instance
const auto error = _instance
? QString::fromStdString(_instance->getLastError())
: QString();
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
@@ -884,7 +934,7 @@ void Call::setSignalBarCount(int count) {
template <typename T>
bool Call::checkCallCommonFields(const T &call) {
auto checkFailed = [this] {
const auto checkFailed = [this] {
finish(FinishType::Failed);
return false;
};
@@ -892,14 +942,22 @@ bool Call::checkCallCommonFields(const T &call) {
LOG(("Call Error: Wrong call access_hash."));
return checkFailed();
}
auto adminId = (_type == Type::Outgoing) ? _user->session().userId() : peerToUser(_user->id);
auto participantId = (_type == Type::Outgoing) ? peerToUser(_user->id) : _user->session().userId();
const auto adminId = (_type == Type::Outgoing)
? _user->session().userId()
: peerToUser(_user->id);
const auto participantId = (_type == Type::Outgoing)
? peerToUser(_user->id)
: _user->session().userId();
if (UserId(call.vadmin_id()) != adminId) {
LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id().v).arg(adminId.bare));
LOG(("Call Error: Wrong call admin_id %1, expected %2.")
.arg(call.vadmin_id().v)
.arg(adminId.bare));
return checkFailed();
}
if (UserId(call.vparticipant_id()) != participantId) {
LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id().v).arg(participantId.bare));
LOG(("Call Error: Wrong call participant_id %1, expected %2.")
.arg(call.vparticipant_id().v)
.arg(participantId.bare));
return checkFailed();
}
return true;
@@ -925,7 +983,8 @@ void Call::setState(State state) {
if (_state.current() == State::Failed) {
return;
}
if (_state.current() == State::FailedHangingUp && state != State::Failed) {
if (_state.current() == State::FailedHangingUp
&& state != State::Failed) {
return;
}
if (_state.current() != state) {
@@ -967,6 +1026,7 @@ void Call::setState(State state) {
break;
case State::Busy:
_delegate->callPlaySound(Delegate::CallSound::Busy);
_discardByTimeoutTimer.cancel();
break;
}
}
@@ -1087,11 +1147,17 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
setSignalBarCount(kSignalBarFinished);
auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed;
auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp;
const auto finalState = (type == FinishType::Ended)
? State::Ended
: State::Failed;
const auto hangupState = (type == FinishType::Ended)
? State::HangingUp
: State::FailedHangingUp;
const auto state = _state.current();
if (state == State::Requesting) {
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] {
setState(finalState);
});
_finishAfterRequestingCall = type;
return;
}
@@ -1108,11 +1174,17 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
}
setState(hangupState);
auto duration = getDurationMs() / 1000;
auto connectionId = _instance ? _instance->getPreferredRelayId() : 0;
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
const auto flags = ((_videoIncoming->state() != Webrtc::VideoState::Inactive)
|| (_videoOutgoing->state() != Webrtc::VideoState::Inactive))
const auto duration = getDurationMs() / 1000;
const auto connectionId = _instance
? _instance->getPreferredRelayId()
: 0;
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] {
setState(finalState);
});
using Video = Webrtc::VideoState;
const auto flags = ((_videoIncoming->state() != Video::Inactive)
|| (_videoOutgoing->state() != Video::Inactive))
? MTPphone_DiscardCall::Flag::f_video
: MTPphone_DiscardCall::Flag(0);
@@ -1150,25 +1222,28 @@ void Call::setFailedQueued(const QString &error) {
});
}
void Call::handleRequestError(const MTP::Error &error) {
if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
Ui::show(Box<InformBox>(tr::lng_call_error_not_available(tr::now, lt_user, _user->name)));
} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) {
Ui::show(Box<InformBox>(tr::lng_call_error_outdated(tr::now, lt_user, _user->name)));
} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)));
void Call::handleRequestError(const QString &error) {
const auto inform = (error == u"USER_PRIVACY_RESTRICTED"_q)
? tr::lng_call_error_not_available(tr::now, lt_user, _user->name)
: (error == u"PARTICIPANT_VERSION_OUTDATED"_q)
? tr::lng_call_error_outdated(tr::now, lt_user, _user->name)
: (error == u"CALL_PROTOCOL_LAYER_INVALID"_q)
? Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)
: QString();
if (!inform.isEmpty()) {
Ui::show(Box<Ui::InformBox>(inform));
}
finish(FinishType::Failed);
}
void Call::handleControllerError(const QString &error) {
if (error == u"ERROR_INCOMPATIBLE"_q) {
Ui::show(Box<InformBox>(
Lang::Hard::CallErrorIncompatible().replace(
"{user}",
_user->name)));
} else if (error == u"ERROR_AUDIO_IO"_q) {
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
const auto inform = (error == u"ERROR_INCOMPATIBLE"_q)
? Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)
: (error == u"ERROR_AUDIO_IO"_q)
? tr::lng_call_error_audio_io(tr::now)
: QString();
if (!inform.isEmpty()) {
Ui::show(Box<Ui::InformBox>(inform));
}
finish(FinishType::Failed);
}

View File

@@ -101,6 +101,9 @@ public:
[[nodiscard]] not_null<UserData*> user() const {
return _user;
}
[[nodiscard]] CallId id() const {
return _id;
}
[[nodiscard]] bool isIncomingWaiting() const;
void start(bytes::const_span random);
@@ -215,7 +218,7 @@ private:
Failed,
};
void handleRequestError(const MTP::Error &error);
void handleRequestError(const QString &error);
void handleControllerError(const QString &error);
void finish(
FinishType type,
@@ -255,7 +258,8 @@ private:
MTP::Sender _api;
Type _type = Type::Outgoing;
rpl::variable<State> _state = State::Starting;
rpl::variable<RemoteAudioState> _remoteAudioState = RemoteAudioState::Active;
rpl::variable<RemoteAudioState> _remoteAudioState =
RemoteAudioState::Active;
rpl::variable<Webrtc::VideoState> _remoteVideoState;
rpl::event_stream<Error> _errors;
FinishType _finishAfterRequestingCall = FinishType::None;
@@ -273,9 +277,8 @@ private:
bytes::vector _gaHash;
bytes::vector _randomPower;
MTP::AuthKey::Data _authKey;
MTPPhoneCallProtocol _protocol;
uint64 _id = 0;
CallId _id = 0;
uint64 _accessHash = 0;
uint64 _keyFingerprint = 0;

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