Compare commits

..

198 Commits

Author SHA1 Message Date
John Preston
9a0023cc99 Version 2.5.7: Support win64 autoupdate. 2021-01-28 17:44:53 +04:00
John Preston
0aea9bc46f Version 2.5.7: Fix build on macOS and Linux. 2021-01-28 16:58:58 +04:00
John Preston
d2662ba1fd Version 2.5.7.
- Delete not only messages, but also groups you created
and call history for all sides, without a trace.
- Adjust volume for individual participants of a voice chat.
- Report fake groups or channels impersonating famous people
or organizations by opening their Profile > ... > Report.
2021-01-28 15:11:56 +04:00
John Preston
0f17a3b300 Update lib_ui submodule. 2021-01-28 12:24:20 +04:00
Ilya Fedin
7c031a4fb6 Perform additional checks for gtk scaling factor 2021-01-28 11:50:52 +04:00
Ilya Fedin
834ee4eae7 Fix crash with fcitx and custom titlebar 2021-01-28 11:45:27 +04:00
Ilya Fedin
9b59e74d66 Make native notifications setting tri-state 2021-01-28 11:44:43 +04:00
Ilya Fedin
ec8ddb047d Use style::CheckScale when setting gtk scale factor 2021-01-28 11:43:17 +04:00
23rd
037506c0b7 Fixed local applying of mute participant state in group calls. 2021-01-28 03:52:53 +03:00
23rd
85b3672bc8 Reduced size of volume speaker in list of participants in group calls. 2021-01-28 02:59:47 +03:00
23rd
bf61f624c5 Added speaking status animation in list of participants in group calls. 2021-01-27 23:48:16 +03:00
John Preston
1baa833e8f Improve selected state of volume changing item. 2021-01-27 23:16:21 +04:00
John Preston
bad2d8afd9 Fix frame rotation in calls from iOS. 2021-01-27 21:19:13 +04:00
23rd
6d5bf53dd1 Added animation of volume percents in menu of group calls. 2021-01-27 20:08:19 +03:00
23rd
3001ad4b89 Improved design of volume info in list of participants in group calls. 2021-01-27 20:05:09 +03:00
23rd
5a88b4f0b9 Added slider sticking to values for volume menu items in group calls. 2021-01-27 20:05:09 +03:00
23rd
71ee981371 Fixed volume percents painting in volume menu item in group calls. 2021-01-27 20:05:09 +03:00
John Preston
e9864bcf5b Update instructions for Windows x64 build. 2021-01-27 20:27:13 +04:00
John Preston
dd401a063b Set preview as cancelled if no preview in editing message. 2021-01-27 20:26:27 +04:00
John Preston
61f6851486 Update settings and report phrases. 2021-01-26 16:33:12 +04:00
John Preston
7da224d725 Allow disabling calls on tdesktop device. 2021-01-26 15:58:30 +04:00
John Preston
ce5c19dfe9 Update API scheme for phone log clearing. 2021-01-26 14:28:25 +04:00
John Preston
52000566cf Don't close calls log box after delete confirmation. 2021-01-26 12:41:52 +04:00
John Preston
fa8dd61b02 All invite links are permanent right now. 2021-01-26 12:24:54 +04:00
John Preston
0d0a79b0b5 Revert "Fixed adding caption to grouped files."
This reverts commit 5277080115.

When sending an album of files each one of them can have its own
caption that will be shown below the file.

A caption for the whole album (in case of media albums) is added
to the first album item. In case of media albums this caption is
displayed under the whole album mosaic. But in case of an album
of files this caption will be displayed under the first file,
between the first and the second file of the album. This is not
what user can expect when he adds a caption for an album.

So we will send it as a separate comment, like it was done before.
2021-01-26 12:15:54 +04:00
John Preston
b22c65a8db Fix build on Windows, add report Fake-s. 2021-01-26 12:15:00 +04:00
23rd
416489a84f Added volume info to list of participants in group calls. 2021-01-25 19:16:57 +03:00
23rd
7424e6afcc Fixed applying active participant state when changing local volume. 2021-01-25 19:16:57 +03:00
23rd
5d1f55e29d Removed display of volume value in menu when participant is muted. 2021-01-25 19:16:57 +03:00
23rd
130b8bc83c Changed slider colors from volume menu item in group calls. 2021-01-25 19:16:57 +03:00
23rd
ef1a4e4ce3 Moved adding volume and mute items in group calls to separate place. 2021-01-25 19:16:31 +03:00
23rd
9fed46fb6e Removed draft volume items from menu in group calls. 2021-01-25 19:16:01 +03:00
23rd
50f87cce84 Added arcs animation to volume menu item in group calls. 2021-01-25 19:16:01 +03:00
23rd
833ffe1784 Added color animation to volume menu item in group calls. 2021-01-25 19:16:01 +03:00
23rd
1d3e76e1fe Added resize handler of volume menu item in group calls. 2021-01-25 19:16:00 +03:00
23rd
1cbb217210 Added speaker icon to volume menu item in group calls. 2021-01-25 19:15:39 +03:00
23rd
70cdc05544 Fixed applying participant locally in group calls. 2021-01-25 19:15:19 +03:00
23rd
173564bcd5 Added initial implementation of volume menu item in group calls. 2021-01-25 19:15:19 +03:00
23rd
e12689c8c1 Added handler of state changes of other participants in group calls. 2021-01-25 19:05:03 +03:00
23rd
e1f5e10764 Added missed implementation of ContinuousSlider::value. 2021-01-25 19:05:03 +03:00
23rd
250add3a96 Added ability to apply volume and mute user in group calls locally only. 2021-01-25 19:05:03 +03:00
23rd
827c950468 Moved common GroupCall structs to separate file. 2021-01-25 19:05:03 +03:00
John Preston
36ad24bfcd Update API scheme. 2021-01-25 17:42:02 +04:00
John Preston
7410c1fc73 Fix display of imported messages in private chats. 2021-01-25 16:50:59 +04:00
John Preston
b2c84d675c Allow clearing calls log. 2021-01-25 16:50:59 +04:00
John Preston
ff9bf23461 Don't show checks for incoming service messages. 2021-01-25 16:50:59 +04:00
John Preston
417428b21d Allow deleting small groups for everyone. 2021-01-25 16:50:59 +04:00
John Preston
58733ba6ea Add support for FAKE badge. 2021-01-25 16:50:59 +04:00
John Preston
1774b21e88 Add ability to completely delete legacy group. 2021-01-25 16:50:58 +04:00
John Preston
19455d44db Add support for imported messages. 2021-01-25 16:50:58 +04:00
John Preston
34f7391ec9 Update API scheme. 2021-01-25 16:50:58 +04:00
John Preston
274779c1c8 Fix build on macOS. 2021-01-25 16:50:58 +04:00
John Preston
50c07bfc98 Update API scheme, add view link box. 2021-01-25 16:50:58 +04:00
John Preston
819cd4a099 Allow deleting revoked invite links. 2021-01-25 16:50:58 +04:00
John Preston
144bad6c74 Update link rows in Manage Invite Links. 2021-01-25 16:50:58 +04:00
John Preston
97fb310f54 Move CalendarBox and ChooseDateTimeBox to td_ui. 2021-01-25 16:50:56 +04:00
John Preston
1cce383d15 Add a box to create / edit invite links. 2021-01-25 16:47:41 +04:00
John Preston
01ecf0ca93 Show invite links list with context menu. 2021-01-25 16:47:41 +04:00
John Preston
40e90af76d Detect tablet mode on Windows 10. 2021-01-25 16:47:41 +04:00
John Preston
7fa342b487 Update API scheme. 2021-01-25 16:47:41 +04:00
John Preston
3862b3b90e Make sharing invite links using ShareBox. 2021-01-25 16:47:41 +04:00
John Preston
5e10d97abe Hide 'No one joined yet.' message. 2021-01-25 16:47:41 +04:00
John Preston
542abb26b9 Allow sharing link to chats. 2021-01-25 16:47:41 +04:00
John Preston
7132ab5bf4 Fix long content in group type box. 2021-01-25 16:47:41 +04:00
John Preston
c7b1a37722 Implement revoke of permanent link. 2021-01-25 16:47:41 +04:00
John Preston
be1afb4781 Show recently joined by permanent link userpics. 2021-01-25 16:47:41 +04:00
John Preston
8c7030378a Add 'Copy Link' and 'Share Link' buttons. 2021-01-25 16:47:41 +04:00
John Preston
7e89ed48c2 Improve permanent link edit design. 2021-01-25 16:47:40 +04:00
John Preston
754dedc40e Improve permanent link edit design. 2021-01-25 16:47:40 +04:00
John Preston
e5320b4b4e Implement new permanent invite link management. 2021-01-25 16:47:40 +04:00
John Preston
02ad5f2772 Update API scheme and start invite links. 2021-01-25 16:47:40 +04:00
John Preston
b58a977029 Remove volume change on muted rows. 2021-01-25 16:47:40 +04:00
John Preston
40fda9503f Apply mute by me / volume change locally. 2021-01-25 16:47:40 +04:00
John Preston
f63f0a7668 Mute by me / change participant volume. 2021-01-25 16:47:40 +04:00
John Preston
b396244606 Update API scheme to layer 123. 2021-01-25 16:47:40 +04:00
Ilya Fedin
b562a4a479 Fix path to libva.conf 2021-01-25 09:42:16 +04:00
Ilya Fedin
82d78e7c45 Decrease indentation in notification manager creation 2021-01-25 09:42:16 +04:00
Ilya Fedin
df0bca077e Fix build with linked gtk 2021-01-25 09:42:16 +04:00
John Preston
efde011f1c Update tg_owt commit in snap build. 2021-01-25 00:51:54 +04:00
23rd
9b9531d279 Replaced icon color for songs with bright one. 2021-01-24 10:41:10 +03:00
23rd
d4bbbdb65c Replaced rand_value util function with openssl::RandomValue. 2021-01-24 10:41:10 +03:00
23rd
c90258664d Removed unused StaticNeverFreedPointer class from utils. 2021-01-24 10:41:10 +03:00
23rd
dd01ece14a Replaced snap util function with std::clamp. 2021-01-24 10:41:10 +03:00
23rd
4895e5e110 Fixed possible crash on deleting messages. 2021-01-24 10:41:10 +03:00
Ilya Fedin
c21125f9f2 Don't log UnknownProperty error
When checking notification inhibition support
2021-01-24 08:51:19 +04:00
Ilya Fedin
ae74f7b3da Build WebRTC without NEON for armhf
ARM support is community maintained in tg_owt and no one seem to fix build with NEON, thus disabling it to get new armhf builds on snapcraft.
2021-01-24 08:49:25 +04:00
Ilya Fedin
8ed56bb4e4 Don't mess GTK scale factor with other scaling settings
Have this order for scaling settings:
1. devicePixelRatio
2. GTK
3. DPI
2021-01-23 21:55:33 +04:00
John Preston
3793f7c3c9 Update tg_owt commit in snap build. 2021-01-23 20:31:24 +04:00
John Preston
0d1b778612 Beta version 2.5.6: Fix build on macOS. 2021-01-23 16:14:37 +04:00
Ilya Fedin
b919a0627a Ensure GtkIntegration::load() is called only once 2021-01-23 16:14:22 +04:00
Ilya Fedin
6374d4eeda Some cosmetic changes in settigs setters 2021-01-23 16:14:22 +04:00
Ilya Fedin
3967052375 Get scale factor from GTK on Linux 2021-01-23 16:14:22 +04:00
Ilya Fedin
89ccc95023 Fix early return from Platform::ThirdParty::start on Linux 2021-01-23 16:14:22 +04:00
John Preston
24f2ca7443 Beta version 2.5.6.
- Press Up arrow to edit your last sent comment.
- Add more information to date tooltips
in Recent Actions and channel comments.
- Bug and crash fixes.
2021-01-22 19:00:11 +04:00
John Preston
f90e13f8b1 Fix crash after account reset after QR login. 2021-01-22 18:19:27 +04:00
John Preston
606f5377d5 Cherry-pick fix for Pulseaudio OpenAL backend. 2021-01-22 18:08:49 +04:00
John Preston
c698327b24 Update tg_owt commit in Snap. 2021-01-22 17:42:06 +04:00
Ilya Fedin
655731741c Add config for lock bot 2021-01-22 17:23:20 +04:00
Ilya Fedin
d5cdb5582b Add config for no-response bot 2021-01-22 17:23:20 +04:00
Ilya Fedin
5cb081ca9a Fix build without dbus 2021-01-22 17:22:37 +04:00
Ilya Fedin
f1e0b36f61 Use operator-> for tray icon biggest size 2021-01-22 17:22:37 +04:00
Ilya Fedin
ea9813825d Move EscapeShell to specific_linux 2021-01-22 17:22:37 +04:00
Ilya Fedin
36b6f70613 Get rid of unneeded includes in specific_linux 2021-01-22 17:22:37 +04:00
Ilya Fedin
5e60b87cf9 Remove platform_specific.h include from mainwindow.h
In order to avoid mass rebuilds on specific_*.h changing
2021-01-22 17:22:37 +04:00
Ilya Fedin
ada22ee6cc Split GTK integration into a singleton 2021-01-22 17:22:37 +04:00
Ilya Fedin
bb016e1489 Restore frameless hint on showing to workaround a bug in Qt 2021-01-22 17:12:53 +04:00
Ilya Fedin
b115ea74d0 Set config dir for OpenSSL and disable OpenSSL DSO
System-provided engines may crash bundled OpenSSL
2021-01-22 17:12:18 +04:00
Ilya Fedin
1008774aef Update vdpau to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
73018ff958 Update libva to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
e799fdaa3d Update wayland-protocols to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
7656a546b0 Update libxkbcommon to latest version 2021-01-22 17:12:18 +04:00
Ilya Fedin
57f9ae4b2a Fix speed control support check 2021-01-22 17:10:22 +04:00
Ilya Fedin
cbdd86d398 Fix deadlock on OpenAL errors 2021-01-22 17:10:22 +04:00
Ilya Fedin
2fe2105a5f Don't add counter when icon theme has 'panel' icon
These icons should have a dot indicating unread messages, counter is redudant for them
2021-01-22 17:09:50 +04:00
Ilya Fedin
a986d7a3d6 Fix checking cover stream on seeking 2021-01-22 17:05:43 +04:00
Ilya Fedin
690c5df87c Format dbus errors logging 2021-01-22 17:02:50 +04:00
Ilya Fedin
1e2759840d Check _sniDBusProxy for nullptr before connecting to signals 2021-01-22 17:02:50 +04:00
Ilya Fedin
bad888496c Decrease some indentation in linux platform code 2021-01-22 17:02:50 +04:00
Ilya Fedin
4348ddf938 Adjust some constexprs in linux platform code 2021-01-22 17:02:50 +04:00
Ilya Fedin
894d6028bd Don't skip native notification toasts 2021-01-22 17:02:50 +04:00
Ilya Fedin
e8edbb16ae Make notification manager creation async 2021-01-22 17:02:50 +04:00
Ilya Fedin
a0a71687e7 Use QDBusPendingReply in GetServerInformation 2021-01-22 17:02:50 +04:00
Ilya Fedin
d042963a47 Make notification show method async 2021-01-22 17:02:50 +04:00
Ilya Fedin
64b12bde55 Allow qualified notification daemons by default on Linux 2021-01-22 17:02:50 +04:00
Ilya Fedin
49736cd879 Recreate notification manager on notification service owner change 2021-01-22 17:02:50 +04:00
the-vyld
e55581e0c9 fix typos in changelog.txt 2021-01-22 16:55:04 +04:00
John Preston
574d915c23 Fix build and tray icon menu on Windows. 2021-01-22 16:53:59 +04:00
23rd
2616659116 Added missed date detailed info in tooltips to admin log and sections. 2021-01-22 16:53:59 +04:00
23rd
3d1f21bd05 Added sent date info to tooltip of messages in admin log.
Fixed #5706.
2021-01-22 16:53:59 +04:00
23rd
dc631ef631 Added ability to see url of inline button in tooltip.
Fixed #5457.
2021-01-22 16:53:59 +04:00
23rd
5277080115 Fixed adding caption to grouped files.
Fixed #10192.
2021-01-22 16:53:59 +04:00
23rd
1ccfcc824c Updated code to be consistent with lib_ui. 2021-01-22 16:53:59 +04:00
23rd
97e8c0956f Moved all files related to menu to separate namespace. 2021-01-22 16:53:59 +04:00
23rd
03a7131a1a Replaced slots with lambdas to fill context menu in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
2d906bddb2 Replaced raw PopupMenu pointer with unique_qptr in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
dd7598a701 Replaced singleShot with InvokeQueued in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
b6f17e1cea Replaced QTimer with base::Timer in OverlayWidget. 2021-01-22 16:53:59 +04:00
23rd
eb42a77eb7 Changed Up arrow shortcut for albums to edit item with caption.
Fixed #10134.
2021-01-22 16:53:59 +04:00
23rd
ad761011d6 Added ability to fetch song cover from external resource. 2021-01-22 16:53:59 +04:00
23rd
3fadf2ee54 Added Up arrow shortcut to edit comments. 2021-01-11 22:46:56 +03:00
23rd
15254599e2 Unified checking for editable message. 2021-01-11 22:46:56 +03:00
23rd
1607752cf9 Added ability to show song cover in inline results. 2021-01-11 22:46:56 +03:00
23rd
cf0cde6e83 Added ability to show song cover in EditCaptionBox and SendFilesBox. 2021-01-11 22:46:56 +03:00
23rd
8fffe7d128 Added ability to show song cover in HistoryView and Overview::Layout. 2021-01-11 22:46:45 +03:00
John Preston
a483eb98a1 Remove not-needed requests for file parts above real size. 2021-01-11 12:38:47 +04:00
John Preston
838a3b23c7 Beta version 2.5.5.
- Fix recording of audio in voice chats.
- Fix media viewer zoom and crashing.
2021-01-10 12:30:58 +04:00
23rd
a030911ad5 Fixed filling context menu in TabbedPanel between sections.
Fixed #10082.
2021-01-09 14:24:41 +03:00
John Preston
8ae1b10b91 Fix media viewer regression. 2021-01-09 13:55:55 +04:00
John Preston
adc8d6a6d1 Fix audio recording in voice chats. 2021-01-08 19:09:45 +04:00
John Preston
1704cb345a Beta version 2.5.4: Fix build on Linux. 2021-01-07 23:17:54 +04:00
John Preston
0b85d0f185 Beta version 2.5.4: Fix build on macOS. 2021-01-07 22:02:22 +04:00
John Preston
348dfefbaa Beta version 2.5.4: Update lib_ui. 2021-01-07 20:07:06 +04:00
John Preston
7508980f62 Beta version 2.5.4.
- Implement new audio module code for calls and voice chats.
- Allow retracting votes from polls in comments to channel posts.
- Show small voice chat button for empty voice chats.
- Fix media viewer updating when screen resolution is changed.
2021-01-07 20:02:21 +04:00
John Preston
e11efe483e Add ability to choose calls audio backend. 2021-01-07 19:27:11 +04:00
John Preston
b23e4fa491 Use OpenAL recording backend for calls on Windows. 2021-01-05 21:15:19 +04:00
John Preston
b6b7f5706f Show small voice chat button for empty voice chats. 2021-01-05 21:15:19 +04:00
John Preston
613bf98283 Fix media viewer controls geometry updating. 2021-01-05 21:15:19 +04:00
23rd
0bdb38753b Added items for polls to context menu in sections.
Fixed #10055.
2021-01-05 21:15:19 +04:00
John Preston
d557e0f2b7 Don't set geometry to OverlayWidget (except macOS). 2021-01-05 21:14:59 +04:00
Ilya Fedin
e81f4e8545 Add updateControls to resizeEvent in media viewer 2021-01-05 18:45:41 +04:00
Ilya Fedin
3b7d5d3c80 Eliminate ifndefs in notifications_manager_linux 2021-01-05 18:16:26 +04:00
Ilya Fedin
0c37990ccd Fix tg_owt cache in windows & macos actions 2021-01-05 18:13:13 +04:00
Ilya Fedin
0fbea454bc Format unity counter setting 2021-01-05 11:43:24 +04:00
Ilya Fedin
d4d688d494 Merge two ifndef blocks in main_window_linux 2021-01-05 11:43:24 +04:00
Ilya Fedin
b3892f49fa Fix kSNIWatcherService/kSNIWatcherInterface misusage
Even though they're the same, there should be interface specified
2021-01-05 11:43:24 +04:00
Ilya Fedin
daa3a2f62f React to resizeEvent in media viewer 2021-01-04 17:33:37 +04:00
Ilya Fedin
5affb168a2 Fix callback function name in open with dialog 2021-01-04 17:08:49 +04:00
Ilya Fedin
99af2a7058 Check for xdg-decoration protocol support on Wayland 2021-01-04 17:08:49 +04:00
Ilya Fedin
b9acea9cef Move GSDMediaKeys initialization to SetWatchingMediaKeys 2021-01-04 11:55:10 +04:00
Ilya Fedin
8fb6ece796 Revert "Use xcb to set transient parent for gtk file dialog"
This reverts commit cd3b989e70.
2021-01-04 11:54:17 +04:00
Ilya Fedin
15a9842b9f Make open with dialog modal on Linux 2021-01-04 11:54:17 +04:00
GitHub Action
b28da30038 Update copyright year to 2021. 2021-01-04 11:24:32 +04:00
GitHub Action
8ce0bd5575 Update User-Agent for DNS to Chrome 87.0.4280.88. 2021-01-04 11:24:11 +04:00
Ilya Fedin
5d68d224e5 Use more --depth=1 in Dockerfile 2021-01-04 11:22:51 +04:00
John Preston
373635a765 Beta version 2.5.3.
- Allow using mouse buttons in Push-to-Talk shortcut.
- Fix blurred thumbnails in Shared Links section.
2020-12-30 17:53:37 +04:00
John Preston
0ecd4d3b40 Close PiP when opening a loading video.
Fixes #9926.
2020-12-30 17:12:40 +04:00
John Preston
3fd62d51aa Hide bot command start button when editing message.
Fixes #9941.
2020-12-30 16:27:32 +04:00
John Preston
818624e051 Don't allow kicking yourself from legacy group. 2020-12-30 16:14:13 +04:00
John Preston
a6eb241ec1 Fix blurred thumbnails in Shared Links section. 2020-12-30 16:13:07 +04:00
John Preston
1d7fb6c4ce Better count voice chat participants count. 2020-12-30 15:53:01 +04:00
John Preston
2af63ec48f Correctly show legacy groups with no admins. 2020-12-30 13:28:35 +04:00
John Preston
0709bc6d70 Fix links in voice chat admin log events. 2020-12-30 13:16:00 +04:00
John Preston
3a34881488 Highlight album part that had a reply clicked. 2020-12-30 12:56:44 +04:00
Ilya Fedin
39f9147790 Check for dbus menu exporter instead of menu path 2020-12-30 11:50:01 +04:00
Ilya Fedin
19a5dcbffc Make OpenAL debugging easier 2020-12-30 11:49:30 +04:00
John Preston
e864aa2ff2 Create audio device module uniformely. 2020-12-30 11:02:18 +04:00
23rd
fe85a8256a Added Github Action that updates copyright year. 2020-12-30 10:59:49 +04:00
23rd
f24b0c6237 Fixed hiding cancel button in state of listen to recorded voice data. 2020-12-30 10:59:49 +04:00
23rd
3940d57c3d Disabled message editing while voice recording. 2020-12-30 10:59:49 +04:00
Ilya Fedin
8da33113a2 Use DeviceModelPretty/SystemVersionPretty directly
This allows using methods that require a running QGuiApplication instance to detect system
2020-12-29 12:36:47 +04:00
Ilya Fedin
f66cfb5684 Use new IsSupportedByWM XCB API from lib_base 2020-12-29 12:29:11 +04:00
John Preston
d648d294ca Fix layout in intro Settings. 2020-12-28 18:29:09 +04:00
John Preston
0c8033414e Allow using mouse buttons in global shortcuts. 2020-12-28 17:06:08 +04:00
Stepan Skryabin
28f29b51c0 Update supported operating systems 2020-12-28 17:01:27 +04:00
Ilya Fedin
8142e83395 Fix connection to QSystemTrayIcon::messageClicked in main_window_win 2020-12-28 17:00:05 +04:00
Ilya Fedin
e247be7e33 Operate with QString instead of QDBusObjectPath 2020-12-28 17:00:05 +04:00
Ilya Fedin
e594b75f4c Use more forward declarations in main_window_linux 2020-12-28 17:00:05 +04:00
Ilya Fedin
28f857f763 Add support for G-S-D's media-keys extension
This fixes media keys handling on (but not limited to, probably):
* GNOME
* Cinnamon
* MATE
* Budgie
* Pantheon (elementaryOS)
* Unity
2020-12-28 17:00:05 +04:00
337 changed files with 11075 additions and 4670 deletions

21
.github/lock.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 45
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: false
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: true

11
.github/no-response.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 30
# Label requiring a response
responseRequiredLabel: waiting for answer
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

View File

@@ -0,0 +1,16 @@
name: Copyright year updater.
on:
repository_dispatch:
types: ["Restart copyright_year_updater workflow."]
schedule:
# At 03:00 on January 1.
- cron: "0 3 1 1 *"
jobs:
Copyright-year:
runs-on: ubuntu-latest
steps:
- uses: desktop-app/action_code_updater@master
with:
type: "license-year"

View File

@@ -102,6 +102,8 @@ jobs:
cd Libraries/macos
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
- name: Patches.
run: |
echo "Find necessary commit from doc."
@@ -475,7 +477,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/tg_owt
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |

View File

@@ -5,152 +5,14 @@ on:
types: ["Restart user_agent_updater workflow."]
schedule:
# At 00:00 on day-of-month 1.
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
pull_request_target:
types: [closed]
jobs:
User-agent:
runs-on: ubuntu-latest
env:
codeFile: "Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp"
headBranchPrefix: "chrome_"
baseBranch: "dev"
isPull: "0"
steps:
- name: Set env.
if: startsWith(github.event_name, 'pull_request')
run: |
echo "isPull=1" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v2
- name: Set up git.
run: |
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
if [ -z "${token}" ]; then
echo "Token is unset. Nothing to do."
exit 1
fi
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
git config --global user.email "action@github.com"
git config --global user.name "GitHub Action"
git remote set-url origin $url
- name: Delete branch.
env:
ref: ${{ github.event.pull_request.head.ref }}
if: |
env.isPull == '1'
&& github.event.action == 'closed'
&& startsWith(env.ref, env.headBranchPrefix)
run: |
git push origin --delete $ref
- name: Write a new version of Google Chrome to the user-agent for DNS.
if: env.isPull == '0'
shell: python
run: |
import subprocess, os, re;
regExpVersion = "[0-9]+.[0-9]+.[0-9]+.[0-9]+";
chrome = "Chrome/";
def newVersion():
output = subprocess.check_output(["google-chrome", "--version"]);
version = re.search(regExpVersion, output);
if not version:
print("Can't find a Chrome version.");
exit();
return version.group(0);
newChromeVersion = newVersion();
print(newChromeVersion);
def setEnv(value):
open(os.environ['GITHUB_ENV'], "a").write(value);
def writeUserAgent():
p = os.environ['codeFile'];
w = open(p, "r");
content = w.read();
w.close();
regExpChrome = chrome + regExpVersion;
version = re.search(regExpChrome, content);
if not version:
print("Can't find an user-agent in the code.");
exit();
content = re.sub(regExpChrome, chrome + newChromeVersion, content);
w = open(p, "w");
w.write(content);
setEnv("ChromeVersion=" + newChromeVersion);
writeUserAgent();
- name: Push to a new branch.
if: env.isPull == '0' && env.ChromeVersion != ''
run: |
git diff > git_diff.txt
if [[ ! -s git_diff.txt ]]; then
echo "Nothing to commit."
exit 0
fi
git checkout -b $headBranchPrefix$ChromeVersion
git add $codeFile
git commit -m "Update User-Agent for DNS to Chrome $ChromeVersion."
git push origin $headBranchPrefix$ChromeVersion
echo "Done!"
- name: Close previous pull requests.
if: env.isPull == '0' && env.ChromeVersion != ''
uses: actions/github-script@0.4.0
- uses: desktop-app/action_code_updater@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const common = {
owner: context.repo.owner,
repo: context.repo.repo,
};
github.pulls.list(common).then(response => {
response.data.forEach((item, _) => {
if (item.head.ref.startsWith(process.env.headBranchPrefix)) {
console.log(`Close ${item.title} #${item.number}.`);
github.pulls.update({
pull_number: item.number,
state: "closed",
...common
});
}
});
});
- name: Create a new pull request.
if: env.isPull == '0' && env.ChromeVersion != ''
uses: actions/github-script@0.4.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const version = process.env.ChromeVersion;
const title = `Update User-Agent for DNS to Chrome ${version}.`;
github.pulls.create({
title: title,
body: "",
head: `${process.env.headBranchPrefix}${version}`,
base: process.env.baseBranch,
owner: context.repo.owner,
repo: context.repo.repo,
});
type: "user-agent"

View File

@@ -103,6 +103,7 @@ jobs:
- name: Generate cache key.
shell: bash
run: |
curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
echo $MANUAL_CACHING >> CACHE_KEY.txt
if [ "$AUTO_CACHING" == "1" ]; then
thisFile=$REPO_NAME/.github/workflows/win.yml
@@ -359,7 +360,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ env.LibrariesPath }}/tg_owt
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
- name: WebRTC.
if: steps.cache-webrtc.outputs.cache-hit != 'true'
run: |

2
LEGAL
View File

@@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2020 The Telegram Desktop Authors.
Copyright (c) 2014-2021 The Telegram Desktop Authors.
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -17,13 +17,17 @@ The latest version is available for
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
* [OS X 10.10 and 10.11](https://telegram.org/dl/desktop/osx)
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux) ([32 bit](https://telegram.org/dl/desktop/linux32))
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
* [Snap](https://snapcraft.io/telegram-desktop)
* [Flatpak](https://flathub.org/apps/details/org.telegram.desktop)
## Old system versions
Version **2.4.4** was the last that supports older systems
* [OS X 10.10 and 10.11](https://updates.tdesktop.com/tosx/tsetup-osx.2.4.4.dmg)
* [Linux static build for 32 bit](https://updates.tdesktop.com/tlinux32/tsetup32.2.4.4.tar.xz)
Version **1.8.15** was the last that supports older systems
* [Windows XP and Vista](https://updates.tdesktop.com/tsetup/tsetup.1.8.15.exe) ([portable](https://updates.tdesktop.com/tsetup/tportable.1.8.15.zip))

View File

@@ -84,6 +84,13 @@ if (LINUX)
)
endif()
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_kwayland
)
endif()
if (DESKTOP_APP_USE_PACKAGED
AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
AND Qt5WaylandClient_VERSION VERSION_LESS 5.13.0)
@@ -138,6 +145,8 @@ PRIVATE
api/api_global_privacy.h
api/api_hash.cpp
api/api_hash.h
api/api_invite_links.cpp
api/api_invite_links.h
api/api_media.cpp
api/api_media.h
api/api_self_destruct.cpp
@@ -172,6 +181,10 @@ PRIVATE
boxes/peers/edit_participants_box.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
@@ -192,8 +205,6 @@ PRIVATE
boxes/background_box.h
boxes/background_preview_box.cpp
boxes/background_preview_box.h
boxes/calendar_box.cpp
boxes/calendar_box.h
boxes/change_phone_box.cpp
boxes/change_phone_box.h
boxes/confirm_box.cpp
@@ -258,6 +269,7 @@ PRIVATE
calls/calls_call.h
calls/calls_group_call.cpp
calls/calls_group_call.h
calls/calls_group_common.h
calls/calls_group_members.cpp
calls/calls_group_members.h
calls/calls_group_panel.cpp
@@ -278,6 +290,8 @@ PRIVATE
calls/calls_userpic.h
calls/calls_video_bubble.cpp
calls/calls_video_bubble.h
calls/calls_volume_item.cpp
calls/calls_volume_item.h
chat_helpers/bot_keyboard.cpp
chat_helpers/bot_keyboard.h
chat_helpers/emoji_keywords.cpp
@@ -812,8 +826,17 @@ PRIVATE
platform/linux/linux_desktop_environment.h
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_libs.cpp
platform/linux/linux_libs.h
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_gtk_integration.h
platform/linux/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.h
platform/linux/linux_open_with_dialog.cpp
platform/linux/linux_open_with_dialog.h
platform/linux/linux_wayland_integration.cpp
platform/linux/linux_wayland_integration.h
platform/linux/linux_xlib_helper.cpp
@@ -882,7 +905,6 @@ PRIVATE
platform/win/windows_dlls.h
platform/win/windows_event_filter.cpp
platform/win/windows_event_filter.h
platform/win/wrapper_wrl_implements_h.h
platform/platform_audio.h
platform/platform_file_utilities.h
platform/platform_launcher.h
@@ -952,6 +974,8 @@ PRIVATE
storage/storage_account.h
storage/storage_cloud_blob.cpp
storage/storage_cloud_blob.h
storage/storage_cloud_song_cover.cpp
storage/storage_cloud_song_cover.h
storage/storage_domain.cpp
storage/storage_domain.h
storage/storage_facade.cpp
@@ -1101,11 +1125,46 @@ if (NOT LINUX)
)
endif()
if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_notification_service_watcher.cpp
platform/linux/linux_notification_service_watcher.h
platform/linux/notifications_manager_linux.cpp
)
nice_target_sources(Telegram ${src_loc}
PRIVATE
platform/linux/notifications_manager_linux_dummy.cpp
)
endif()
if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp)
endif()
if (LINUX AND TDESKTOP_DISABLE_GTK_INTEGRATION)
remove_target_sources(Telegram ${src_loc}
platform/linux/linux_gdk_helper.cpp
platform/linux/linux_gdk_helper.h
platform/linux/linux_gtk_file_dialog.cpp
platform/linux/linux_gtk_file_dialog.h
platform/linux/linux_gtk_integration_p.h
platform/linux/linux_gtk_integration.cpp
platform/linux/linux_open_with_dialog.cpp
platform/linux/linux_open_with_dialog.h
platform/linux/linux_xlib_helper.cpp
platform/linux/linux_xlib_helper.h
)
nice_target_sources(Telegram ${src_loc}
PRIVATE
platform/linux/linux_gtk_integration_dummy.cpp
)
endif()
if (NOT DESKTOP_APP_USE_PACKAGED)
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

View File

@@ -126,6 +126,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_status" = "channel";
"lng_group_status" = "group";
"lng_scam_badge" = "SCAM";
"lng_fake_badge" = "FAKE";
"lng_flood_error" = "Too many tries. Please try again later.";
"lng_gif_error" = "An error has occurred while reading GIF animation :(";
@@ -327,6 +328,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_events_title" = "Events";
"lng_settings_events_joined" = "Contact joined Telegram";
"lng_settings_events_pinned" = "Pinned messages";
"lng_settings_notifications_calls_title" = "Calls";
"lng_notification_preview" = "You have a new message";
"lng_notification_reply" = "Reply";
@@ -394,6 +396,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_call_stop_mic_test" = "Stop test";
"lng_settings_call_section_other" = "Other settings";
"lng_settings_call_open_system_prefs" = "Open system sound preferences";
"lng_settings_call_accept_calls" = "Accept calls from this device";
"lng_settings_call_device_default" = "Same as the System";
"lng_settings_call_audio_ducking" = "Mute other sounds during calls";
@@ -932,6 +935,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_exceptions" = "Exceptions";
"lng_manage_peer_removed_users" = "Removed users";
"lng_manage_peer_permissions" = "Permissions";
"lng_manage_peer_invite_links" = "Invite links";
"lng_manage_peer_group_type" = "Group type";
"lng_manage_peer_channel_type" = "Channel type";
@@ -975,6 +979,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_bot_title" = "Report bot";
"lng_report_message_title" = "Report message";
"lng_report_reason_spam" = "Spam";
"lng_report_reason_fake" = "Fake Account";
"lng_report_reason_violence" = "Violence";
"lng_report_reason_child_abuse" = "Child Abuse";
"lng_report_reason_pornography" = "Pornography";
@@ -1164,14 +1169,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_invite_members#other" = "{count} members, among them:";
"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
"lng_group_invite_create" = "Create an invite link";
"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link.";
"lng_group_invite_about_channel" = "Telegram users will be able to join\nyour channel by following this link.";
"lng_group_invite_create_new" = "Revoke invite link";
"lng_group_invite_create" = "Create an invite link"; // #TODO links legacy
"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
"lng_group_invite_copied" = "Invite link copied to clipboard.";
"lng_group_invite_no_room" = "Unable to join this group because there are too many members in it already.";
"lng_group_invite_permanent" = "Permanent link";
"lng_group_invite_copy" = "Copy Link";
"lng_group_invite_share" = "Share Link";
"lng_group_invite_revoke" = "Revoke Link";
"lng_group_invite_no_joined" = "No one joined yet";
"lng_group_invite_joined#one" = "{count} person joined";
"lng_group_invite_joined#other" = "{count} people joined";
"lng_group_invite_about_permanent_group" = "Anyone who has Telegram installed will be able to join your group by following this link.";
"lng_group_invite_about_permanent_channel" = "Anyone who has Telegram installed will be able to join your channel by following this link.";
"lng_group_invite_manage" = "Manage Invite Links";
"lng_group_invite_manage_about" = "You can create additional invite links that have limited time or number of usages.";
"lng_group_invite_title" = "Invite links";
"lng_group_invite_add" = "Create a New Link";
"lng_group_invite_add_about" = "You can generate invite links that will expire after they've been used.";
"lng_group_invite_expires_at" = "This link expires {when}.";
"lng_group_invite_expired_already" = "This link has expired.";
"lng_group_invite_created_by" = "Link created by";
"lng_group_invite_context_copy" = "Copy";
"lng_group_invite_context_share" = "Share";
"lng_group_invite_context_edit" = "Edit";
"lng_group_invite_context_revoke" = "Revoke";
"lng_group_invite_context_delete" = "Delete";
"lng_group_invite_context_delete_all" = "Delete all";
"lng_group_invite_delete_sure" = "Are you sure you want to delete that revoked link?";
"lng_group_invite_delete_all_sure" = "Are you sure you want to delete all revoked links? This action cannot be undone.";
"lng_group_invite_revoked_title" = "Revoked links";
"lng_group_invite_revoke_about" = "Are you sure you want to revoke that invite link?";
"lng_group_invite_link_expired" = "Expired";
"lng_group_invite_link_revoked" = "Revoked";
"lng_group_invite_edit_title" = "Edit Link";
"lng_group_invite_new_title" = "New Link";
"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";
"lng_group_invite_expire_custom" = "Custom";
"lng_group_invite_usage_title" = "Limit number of uses";
"lng_group_invite_usage_about" = "You can make the link expire after it has been used for a certain number of times.";
"lng_group_invite_expire_after" = "Expire after";
"lng_group_invite_custom_limit" = "Enter custom limit";
"lng_group_invite_usage_any" = "No limit";
"lng_group_invite_usage_custom" = "Custom";
"lng_channel_public_link_copied" = "Link copied to clipboard.";
"lng_context_about_private_link" = "This link will only work for members of this chat.";
@@ -1183,10 +1227,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forwarded_channel_via" = "Forwarded from {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_forwarded_hidden" = "The account was hidden by the user.";
"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_edited" = "edited";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
"lng_imported" = "imported";
"lng_admin_badge" = "admin";
"lng_owner_badge" = "owner";
"lng_channel_badge" = "channel";
@@ -1786,6 +1833,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_box_status_yesterday" = "Yesterday at {time}";
"lng_call_box_status_date" = "{date} at {time}";
"lng_call_box_status_group" = "({amount}) {status}";
"lng_call_box_clear_all" = "Clear All";
"lng_call_box_clear_sure" = "Are you sure you want to completely clear your calls log?";
"lng_call_box_clear_button" = "Clear";
"lng_call_outgoing" = "Outgoing call";
"lng_call_video_outgoing" = "Outgoing video call";
@@ -1834,6 +1884,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_settings_title" = "Settings";
"lng_group_call_invite" = "Invite Member";
"lng_group_call_invited_status" = "invited";
"lng_group_call_muted_by_me_status" = "muted by me";
"lng_group_call_invite_title" = "Invite members";
"lng_group_call_invite_button" = "Invite";
"lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}» yet. Add them to the group?";
@@ -1863,6 +1914,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_too_many" = "Sorry, there are too many members in this voice chat. Please try again later.";
"lng_group_call_context_mute" = "Mute";
"lng_group_call_context_unmute" = "Allow to speak";
"lng_group_call_context_mute_for_me" = "Mute for me";
"lng_group_call_context_unmute_for_me" = "Unmute for me";
"lng_group_call_duration_days#one" = "{count} day";
"lng_group_call_duration_days#other" = "{count} days";
"lng_group_call_duration_hours#one" = "{count} hour";

View File

@@ -109,7 +109,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
userEmpty#200250ba id:int = User;
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
@@ -124,11 +124,11 @@ userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#9ba2d800 id:int = Chat;
chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#7328bdb id:int title:string = Chat;
channel#d31a961e 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 id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version: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;
channel#d31a961e 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 id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version: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#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull;
channelFull#ef3a6acd 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:int 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:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int 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?int 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 = ChatFull;
chatFull#f3474af6 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int 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 = ChatFull;
channelFull#7a7de4f7 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:int 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?int 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?int 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 = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@@ -140,7 +140,7 @@ chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> versi
chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
messageEmpty#83e5de54 id:int = Message;
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message;
@@ -217,7 +217,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings;
peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings;
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
@@ -229,6 +229,7 @@ inputReportReasonChildAbuse#adf44ee3 = ReportReason;
inputReportReasonOther#e1746d0a text:string = ReportReason;
inputReportReasonCopyright#9b89f93a = ReportReason;
inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
inputReportReasonFake#f5ddd6e7 = ReportReason;
userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
@@ -407,7 +408,7 @@ encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = EncryptedChat;
inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat;
@@ -455,6 +456,7 @@ sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
speakingInGroupCallAction#d92c2285 = SendMessageAction;
sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction;
contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
@@ -535,8 +537,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
chatInviteEmpty#69df3769 = ExportedChatInvite;
chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
chatInviteExported#6e24fc9d flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:int date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = 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;
@@ -661,7 +662,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off
exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink;
messageFwdHeader#5f777dce flags:# from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader;
messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader;
auth.codeTypeSms#72a3158c = auth.CodeType;
auth.codeTypeCall#741cd3e3 = auth.CodeType;
@@ -820,7 +821,7 @@ payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_inf
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
inputPaymentCredentialsGooglePay#8ac32801 payment_token:DataJSON = InputPaymentCredentials;
account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
@@ -1195,7 +1196,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true muted_by_you:flags.9?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant;
phone.groupCall#66ab0bfc call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string users:Vector<User> = phone.GroupCall;
@@ -1207,6 +1208,12 @@ inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType;
inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType;
inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType;
messages.historyImport#1662af0b id:long = messages.HistoryImport;
messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true title:flags.2?string = messages.HistoryImportParsed;
messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector<int> = messages.AffectedFoundMessages;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1349,12 +1356,12 @@ messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
messages.editChatTitle#dc452855 chat_id:int title:string = Updates;
messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates;
messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates;
messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates;
messages.deleteChatUser#c534459a flags:# revoke_history:flags.0?true chat_id:int user_id:InputUser = Updates;
messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
messages.discardEncryption#edd923c5 chat_id:int = Bool;
messages.discardEncryption#f393aea0 flags:# delete_history:flags.0?true chat_id:int = Bool;
messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool;
messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool;
messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
@@ -1456,6 +1463,12 @@ messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
messages.deleteChat#83247d11 chat_id:int = Bool;
messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages;
messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed;
messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport;
messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia;
messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1507,7 +1520,7 @@ channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipant
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates;
channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
@@ -1564,7 +1577,7 @@ phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates;
phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates;
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates;
phone.editGroupCallMember#a5e76cd8 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser volume:flags.1?int = Updates;
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates;
@@ -1587,4 +1600,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 122
// LAYER 123

View File

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

View File

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

View File

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

View File

@@ -149,6 +149,7 @@ int main(int argc, char *argv[])
QString remove;
int version = 0;
bool targetosx = false;
bool targetwin64 = false;
QFileInfoList files;
for (int i = 0; i < argc; ++i) {
if (string("-path") == argv[i] && i + 1 < argc) {
@@ -158,6 +159,7 @@ int main(int argc, char *argv[])
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
} else if (string("-target") == argv[i] && i + 1 < argc) {
targetosx = (string("osx") == argv[i + 1]);
targetwin64 = (string("win64") == argv[i + 1]);
} else if (string("-version") == argv[i] && i + 1 < argc) {
version = QString(argv[i + 1]).toInt();
} else if (string("-beta") == argv[i]) {
@@ -464,7 +466,7 @@ int main(int argc, char *argv[])
cout << "Signature verified!\n";
RSA_free(pbKey);
#ifdef Q_OS_WIN
QString outName(QString("tupdate%1").arg(AlphaVersion ? AlphaVersion : version));
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_MAC
QString outName((targetosx ? QString("tosxupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_UNIX

View File

@@ -0,0 +1,631 @@
/*
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_invite_links.h"
#include "data/data_peer.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "main/main_session.h"
#include "apiwrap.h"
namespace Api {
namespace {
constexpr auto kFirstPage = 10;
constexpr auto kPerPage = 50;
constexpr auto kJoinedFirstPage = 10;
void BringPermanentToFront(PeerInviteLinks &links) {
auto &list = links.links;
const auto i = ranges::find_if(list, [](const InviteLink &link) {
return link.permanent && !link.revoked;
});
if (i != end(list) && i != begin(list)) {
ranges::rotate(begin(list), i, i + 1);
}
}
void RemovePermanent(PeerInviteLinks &links) {
auto &list = links.links;
list.erase(ranges::remove_if(list, [](const InviteLink &link) {
return link.permanent && !link.revoked;
}), end(list));
}
} // namespace
// #TODO links
//JoinedByLinkSlice ParseJoinedByLinkSlice(
// not_null<PeerData*> peer,
// const MTPmessages_ChatInviteImporters &slice) {
// auto result = JoinedByLinkSlice();
// slice.match([&](const MTPDmessages_chatInviteImporters &data) {
// auto &owner = peer->session().data();
// owner.processUsers(data.vusers());
// result.count = data.vcount().v;
// result.users.reserve(data.vimporters().v.size());
// for (const auto importer : data.vimporters().v) {
// importer.match([&](const MTPDchatInviteImporter &data) {
// result.users.push_back({
// .user = owner.user(data.vuser_id().v),
// .date = data.vdate().v,
// });
// });
// }
// });
// return result;
//}
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
}
void InviteLinks::create(
not_null<PeerData*> peer,
Fn<void(Link)> done,
TimeId expireDate,
int usageLimit) {
performCreate(peer, std::move(done), false, expireDate, usageLimit);
}
void InviteLinks::performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
TimeId expireDate,
int usageLimit) {
if (const auto i = _createCallbacks.find(peer)
; i != end(_createCallbacks)) {
if (done) {
i->second.push_back(std::move(done));
}
return;
}
auto &callbacks = _createCallbacks[peer];
if (done) {
callbacks.push_back(std::move(done));
}
// #TODO links
//using Flag = MTPmessages_ExportChatInvite::Flag;
//_api->request(MTPmessages_ExportChatInvite(
// MTP_flags((revokeLegacyPermanent
// ? Flag::f_legacy_revoke_permanent
// : Flag(0))
// | (expireDate ? Flag::f_expire_date : Flag(0))
// | (usageLimit ? Flag::f_usage_limit : Flag(0))),
// peer->input,
// MTP_int(expireDate),
// MTP_int(usageLimit)
_api->request(MTPmessages_ExportChatInvite(
peer->input
)).done([=](const MTPExportedChatInvite &result) {
const auto callbacks = _createCallbacks.take(peer);
const auto link = prepend(peer, result);
if (callbacks) {
for (const auto &callback : *callbacks) {
callback(link);
}
}
}).fail([=](const RPCError &error) {
_createCallbacks.erase(peer);
}).send();
}
auto InviteLinks::lookupPermanent(not_null<PeerData*> peer) -> Link* {
auto i = _firstSlices.find(peer);
return (i != end(_firstSlices)) ? lookupPermanent(i->second) : nullptr;
}
auto InviteLinks::lookupPermanent(Links &links) -> Link* {
const auto first = links.links.begin();
return (first != end(links.links) && first->permanent && !first->revoked)
? &*first
: nullptr;
}
auto InviteLinks::lookupPermanent(const Links &links) const -> const Link* {
const auto first = links.links.begin();
return (first != end(links.links) && first->permanent && !first->revoked)
? &*first
: nullptr;
}
auto InviteLinks::prepend(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite) -> Link {
const auto link = parse(peer, invite);
auto i = _firstSlices.find(peer);
if (i == end(_firstSlices)) {
i = _firstSlices.emplace(peer).first;
}
auto &links = i->second;
const auto permanent = lookupPermanent(links);
const auto hadPermanent = (permanent != nullptr);
auto updateOldPermanent = Update{ .peer = peer };
if (link.permanent && hadPermanent) {
updateOldPermanent.was = permanent->link;
updateOldPermanent.now = *permanent;
updateOldPermanent.now->revoked = true;
links.links.erase(begin(links.links));
if (links.count > 0) {
--links.count;
}
}
// Must not dereference 'permanent' pointer after that.
++links.count;
if (hadPermanent && !link.permanent) {
links.links.insert(begin(links.links) + 1, link);
} else {
links.links.insert(begin(links.links), link);
}
if (link.permanent) {
editPermanentLink(peer, link.link);
}
notify(peer);
if (updateOldPermanent.now) {
_updates.fire(std::move(updateOldPermanent));
}
_updates.fire(Update{ .peer = peer, .now = link });
return link;
}
void InviteLinks::edit(
not_null<PeerData*> peer,
const QString &link,
TimeId expireDate,
int usageLimit,
Fn<void(Link)> done) {
performEdit(peer, link, std::move(done), false, expireDate, usageLimit);
}
void InviteLinks::performEdit(
not_null<PeerData*> peer,
const QString &link,
Fn<void(Link)> done,
bool revoke,
TimeId expireDate,
int usageLimit) {
const auto key = LinkKey{ peer, link };
if (_deleteCallbacks.contains(key)) {
return;
} else if (const auto i = _editCallbacks.find(key)
; i != end(_editCallbacks)) {
if (done) {
i->second.push_back(std::move(done));
}
return;
}
if (const auto permanent = revoke ? lookupPermanent(peer) : nullptr) {
if (permanent->link == link) {
// In case of revoking a permanent link
// we should just create a new one instead.
performCreate(peer, std::move(done), true);
return;
}
}
auto &callbacks = _editCallbacks[key];
if (done) {
callbacks.push_back(std::move(done));
}
// #TODO links
//using Flag = MTPmessages_EditExportedChatInvite::Flag;
//_api->request(MTPmessages_EditExportedChatInvite(
// MTP_flags((revoke ? Flag::f_revoked : Flag(0))
// | ((!revoke && expireDate) ? Flag::f_expire_date : Flag(0))
// | ((!revoke && usageLimit) ? Flag::f_usage_limit : Flag(0))),
// peer->input,
// MTP_string(link),
// MTP_int(expireDate),
// MTP_int(usageLimit)
//)).done([=](const MTPmessages_ExportedChatInvite &result) {
// const auto callbacks = _editCallbacks.take(key);
// const auto peer = key.peer;
// result.match([&](const MTPDmessages_exportedChatInvite &data) {
// _api->session().data().processUsers(data.vusers());
// const auto link = parse(peer, data.vinvite());
// auto i = _firstSlices.find(peer);
// if (i != end(_firstSlices)) {
// const auto j = ranges::find(
// i->second.links,
// key.link,
// &Link::link);
// if (j != end(i->second.links)) {
// if (link.revoked && !j->revoked) {
// i->second.links.erase(j);
// if (i->second.count > 0) {
// --i->second.count;
// }
// } else {
// *j = link;
// }
// }
// }
// for (const auto &callback : *callbacks) {
// callback(link);
// }
// _updates.fire(Update{
// .peer = peer,
// .was = key.link,
// .now = link,
// });
// });
//}).fail([=](const RPCError &error) {
// _editCallbacks.erase(key);
//}).send();
}
void InviteLinks::revoke(
not_null<PeerData*> peer,
const QString &link,
Fn<void(Link)> done) {
performEdit(peer, link, std::move(done), true);
}
void InviteLinks::revokePermanent(
not_null<PeerData*> peer,
Fn<void(Link)> done) {
performCreate(peer, std::move(done), true);
}
void InviteLinks::destroy(
not_null<PeerData*> peer,
const QString &link,
Fn<void()> done) {
const auto key = LinkKey{ peer, link };
if (const auto i = _deleteCallbacks.find(key)
; i != end(_deleteCallbacks)) {
if (done) {
i->second.push_back(std::move(done));
}
return;
}
auto &callbacks = _deleteCallbacks[key];
if (done) {
callbacks.push_back(std::move(done));
}
// #TODO links
//_api->request(MTPmessages_DeleteExportedChatInvite(
// peer->input,
// MTP_string(link)
//)).done([=](const MTPBool &result) {
// const auto callbacks = _deleteCallbacks.take(key);
// if (callbacks) {
// for (const auto &callback : *callbacks) {
// callback();
// }
// }
// _updates.fire(Update{
// .peer = peer,
// .was = key.link,
// });
//}).fail([=](const RPCError &error) {
// _deleteCallbacks.erase(key);
//}).send();
}
void InviteLinks::destroyAllRevoked(
not_null<PeerData*> peer,
Fn<void()> done) {
if (const auto i = _deleteRevokedCallbacks.find(peer)
; i != end(_deleteRevokedCallbacks)) {
if (done) {
i->second.push_back(std::move(done));
}
return;
}
auto &callbacks = _deleteRevokedCallbacks[peer];
if (done) {
callbacks.push_back(std::move(done));
}
// #TODO links
//_api->request(MTPmessages_DeleteRevokedExportedChatInvites(
// peer->input
//)).done([=](const MTPBool &result) {
// if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) {
// for (const auto &callback : *callbacks) {
// callback();
// }
// }
// _allRevokedDestroyed.fire_copy(peer);
//}).fail([=](const RPCError &error) {
//}).send();
}
void InviteLinks::requestLinks(not_null<PeerData*> peer) {
if (_firstSliceRequests.contains(peer)) {
return;
}
// #TODO links
//const auto requestId = _api->request(MTPmessages_GetExportedChatInvites(
// MTP_flags(0),
// peer->input,
// MTPInputUser(), // admin_id
// MTPint(), // offset_date
// MTPstring(), // offset_link
// MTP_int(kFirstPage)
//)).done([=](const MTPmessages_ExportedChatInvites &result) {
// _firstSliceRequests.remove(peer);
// auto slice = parseSlice(peer, result);
// auto i = _firstSlices.find(peer);
// const auto permanent = (i != end(_firstSlices))
// ? lookupPermanent(i->second)
// : nullptr;
// if (!permanent) {
// BringPermanentToFront(slice);
// const auto j = _firstSlices.emplace_or_assign(
// peer,
// std::move(slice)).first;
// if (const auto permanent = lookupPermanent(j->second)) {
// editPermanentLink(peer, permanent->link);
// }
// } else {
// RemovePermanent(slice);
// auto &existing = i->second.links;
// existing.erase(begin(existing) + 1, end(existing));
// existing.insert(
// end(existing),
// begin(slice.links),
// end(slice.links));
// i->second.count = std::max(slice.count, int(existing.size()));
// }
// notify(peer);
//}).fail([=](const RPCError &error) {
// _firstSliceRequests.remove(peer);
//}).send();
//_firstSliceRequests.emplace(peer, requestId);
}
std::optional<JoinedByLinkSlice> InviteLinks::lookupJoinedFirstSlice(
LinkKey key) const {
const auto i = _firstJoined.find(key);
return (i != end(_firstJoined))
? std::make_optional(i->second)
: std::nullopt;
}
std::optional<JoinedByLinkSlice> InviteLinks::joinedFirstSliceLoaded(
not_null<PeerData*> peer,
const QString &link) const {
return lookupJoinedFirstSlice({ peer, link });
}
rpl::producer<JoinedByLinkSlice> InviteLinks::joinedFirstSliceValue(
not_null<PeerData*> peer,
const QString &link,
int fullCount) {
const auto key = LinkKey{ peer, link };
auto current = lookupJoinedFirstSlice(key).value_or(JoinedByLinkSlice());
if (current.count == fullCount
&& (!fullCount || !current.users.empty())) {
return rpl::single(current);
}
current.count = fullCount;
const auto remove = int(current.users.size()) - current.count;
if (remove > 0) {
current.users.erase(end(current.users) - remove, end(current.users));
}
requestJoinedFirstSlice(key);
using namespace rpl::mappers;
return rpl::single(
current
) | rpl::then(_joinedFirstSliceLoaded.events(
) | rpl::filter(
_1 == key
) | rpl::map([=] {
return lookupJoinedFirstSlice(key).value_or(JoinedByLinkSlice());
}));
}
auto InviteLinks::updates(
not_null<PeerData*> peer) const -> rpl::producer<Update> {
return _updates.events() | rpl::filter([=](const Update &update) {
return update.peer == peer;
});
}
rpl::producer<> InviteLinks::allRevokedDestroyed(
not_null<PeerData*> peer) const {
using namespace rpl::mappers;
return _allRevokedDestroyed.events(
) | rpl::filter(
_1 == peer
) | rpl::to_empty;
}
void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
//if (_firstJoinedRequests.contains(key)) { // #TODO links
// return;
//}
//const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
// key.peer->input,
// MTP_string(key.link),
// MTP_int(0), // offset_date
// MTP_inputUserEmpty(), // offset_user
// MTP_int(kJoinedFirstPage)
//)).done([=](const MTPmessages_ChatInviteImporters &result) {
// _firstJoinedRequests.remove(key);
// _firstJoined[key] = ParseJoinedByLinkSlice(key.peer, result);
// _joinedFirstSliceLoaded.fire_copy(key);
//}).fail([=](const RPCError &error) {
// _firstJoinedRequests.remove(key);
//}).send();
//_firstJoinedRequests.emplace(key, requestId);
}
void InviteLinks::setPermanent(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite) {
auto link = parse(peer, invite);
if (!link.permanent) {
LOG(("API Error: "
"InviteLinks::setPermanent called with non-permanent link."));
return;
}
auto i = _firstSlices.find(peer);
if (i == end(_firstSlices)) {
i = _firstSlices.emplace(peer).first;
}
auto &links = i->second;
auto updateOldPermanent = Update{ .peer = peer };
if (const auto permanent = lookupPermanent(links)) {
if (permanent->link == link.link) {
if (permanent->usage != link.usage) {
permanent->usage = link.usage;
_updates.fire(Update{
.peer = peer,
.was = link.link,
.now = *permanent
});
}
return;
}
updateOldPermanent.was = permanent->link;
updateOldPermanent.now = *permanent;
updateOldPermanent.now->revoked = true;
links.links.erase(begin(links.links));
if (links.count > 0) {
--links.count;
}
}
links.links.insert(begin(links.links), link);
editPermanentLink(peer, link.link);
notify(peer);
if (updateOldPermanent.now) {
_updates.fire(std::move(updateOldPermanent));
}
_updates.fire(Update{ .peer = peer, .now = link });
}
void InviteLinks::clearPermanent(not_null<PeerData*> peer) {
auto i = _firstSlices.find(peer);
if (i == end(_firstSlices)) {
return;
}
auto &links = i->second;
const auto permanent = lookupPermanent(links);
if (!permanent) {
return;
}
auto updateOldPermanent = Update{ .peer = peer };
updateOldPermanent.was = permanent->link;
updateOldPermanent.now = *permanent;
updateOldPermanent.now->revoked = true;
links.links.erase(begin(links.links));
if (links.count > 0) {
--links.count;
}
editPermanentLink(peer, QString());
notify(peer);
if (updateOldPermanent.now) {
_updates.fire(std::move(updateOldPermanent));
}
}
void InviteLinks::notify(not_null<PeerData*> peer) {
peer->session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::InviteLinks);
}
auto InviteLinks::links(not_null<PeerData*> peer) const -> const Links & {
static const auto kEmpty = Links();
const auto i = _firstSlices.find(peer);
return (i != end(_firstSlices)) ? i->second : kEmpty;
}
// #TODO links
//auto InviteLinks::parseSlice(
// not_null<PeerData*> peer,
// const MTPmessages_ExportedChatInvites &slice) const -> Links {
// auto i = _firstSlices.find(peer);
// const auto permanent = (i != end(_firstSlices))
// ? lookupPermanent(i->second)
// : nullptr;
// auto result = Links();
// slice.match([&](const MTPDmessages_exportedChatInvites &data) {
// peer->session().data().processUsers(data.vusers());
// result.count = data.vcount().v;
// for (const auto &invite : data.vinvites().v) {
// const auto link = parse(peer, invite);
// if (!permanent || link.link != permanent->link) {
// result.links.push_back(link);
// }
// }
// });
// return result;
//}
auto InviteLinks::parse(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite) const -> Link {
return invite.match([&](const MTPDchatInviteExported &data) {
return Link{
.link = qs(data.vlink()),
.admin = peer->session().data().user(data.vadmin_id().v),
.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(),
.permanent = true,//data.is_permanent(), // #TODO links
.revoked = data.is_revoked(),
};
});
}
void InviteLinks::requestMoreLinks(
not_null<PeerData*> peer,
TimeId lastDate,
const QString &lastLink,
bool revoked,
Fn<void(Links)> done) {
// #TODO links
//using Flag = MTPmessages_GetExportedChatInvites::Flag;
//_api->request(MTPmessages_GetExportedChatInvites(
// MTP_flags(Flag::f_offset_link
// | (revoked ? Flag::f_revoked : Flag(0))),
// peer->input,
// MTPInputUser(), // admin_id,
// MTP_int(lastDate),
// MTP_string(lastLink),
// MTP_int(kPerPage)
//)).done([=](const MTPmessages_ExportedChatInvites &result) {
// auto slice = parseSlice(peer, result);
// RemovePermanent(slice);
// done(std::move(slice));
//}).fail([=](const RPCError &error) {
// done(Links());
//}).send();
}
void InviteLinks::editPermanentLink(
not_null<PeerData*> peer,
const QString &link) {
if (const auto chat = peer->asChat()) {
chat->setInviteLink(link);
} else if (const auto channel = peer->asChannel()) {
channel->setInviteLink(link);
} else {
Unexpected("Peer in InviteLinks::editMainLink.");
}
}
} // namespace Api

View File

@@ -0,0 +1,187 @@
/*
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
class ApiWrap;
namespace Api {
struct InviteLink {
QString link;
not_null<UserData*> admin;
TimeId date = 0;
TimeId startDate = 0;
TimeId expireDate = 0;
int usageLimit = 0;
int usage = 0;
bool permanent = false;
bool revoked = false;
};
struct PeerInviteLinks {
std::vector<InviteLink> links;
int count = 0;
};
struct JoinedByLinkUser {
not_null<UserData*> user;
TimeId date = 0;
};
struct JoinedByLinkSlice {
std::vector<JoinedByLinkUser> users;
int count = 0;
};
struct InviteLinkUpdate {
not_null<PeerData*> peer;
QString was;
std::optional<InviteLink> now;
};
// #TODO links
//[[nodiscard]] JoinedByLinkSlice ParseJoinedByLinkSlice(
// not_null<PeerData*> peer,
// const MTPmessages_ChatInviteImporters &slice);
class InviteLinks final {
public:
explicit InviteLinks(not_null<ApiWrap*> api);
using Link = InviteLink;
using Links = PeerInviteLinks;
using Update = InviteLinkUpdate;
void create(
not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr,
TimeId expireDate = 0,
int usageLimit = 0);
void edit(
not_null<PeerData*> peer,
const QString &link,
TimeId expireDate,
int usageLimit,
Fn<void(Link)> done = nullptr);
void revoke(
not_null<PeerData*> peer,
const QString &link,
Fn<void(Link)> done = nullptr);
void revokePermanent(
not_null<PeerData*> peer,
Fn<void(Link)> done = nullptr);
void destroy(
not_null<PeerData*> peer,
const QString &link,
Fn<void()> done = nullptr);
void destroyAllRevoked(
not_null<PeerData*> peer,
Fn<void()> done = nullptr);
void setPermanent(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite);
void clearPermanent(not_null<PeerData*> peer);
void requestLinks(not_null<PeerData*> peer);
[[nodiscard]] const Links &links(not_null<PeerData*> peer) const;
[[nodiscard]] rpl::producer<JoinedByLinkSlice> joinedFirstSliceValue(
not_null<PeerData*> peer,
const QString &link,
int fullCount);
[[nodiscard]] std::optional<JoinedByLinkSlice> joinedFirstSliceLoaded(
not_null<PeerData*> peer,
const QString &link) const;
[[nodiscard]] rpl::producer<Update> updates(
not_null<PeerData*> peer) const;
[[nodiscard]] rpl::producer<> allRevokedDestroyed(
not_null<PeerData*> peer) const;
void requestMoreLinks(
not_null<PeerData*> peer,
TimeId lastDate,
const QString &lastLink,
bool revoked,
Fn<void(Links)> done);
private:
struct LinkKey {
not_null<PeerData*> peer;
QString link;
friend inline bool operator<(const LinkKey &a, const LinkKey &b) {
return (a.peer == b.peer)
? (a.link < b.link)
: (a.peer < b.peer);
}
friend inline bool operator==(const LinkKey &a, const LinkKey &b) {
return (a.peer == b.peer) && (a.link == b.link);
}
};
// #TODO links
//[[nodiscard]] Links parseSlice(
// not_null<PeerData*> peer,
// const MTPmessages_ExportedChatInvites &slice) const;
[[nodiscard]] Link parse(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite) const;
[[nodiscard]] Link *lookupPermanent(not_null<PeerData*> peer);
[[nodiscard]] Link *lookupPermanent(Links &links);
[[nodiscard]] const Link *lookupPermanent(const Links &links) const;
Link prepend(
not_null<PeerData*> peer,
const MTPExportedChatInvite &invite);
void notify(not_null<PeerData*> peer);
void editPermanentLink(
not_null<PeerData*> peer,
const QString &link);
void performEdit(
not_null<PeerData*> peer,
const QString &link,
Fn<void(Link)> done,
bool revoke,
TimeId expireDate = 0,
int usageLimit = 0);
void performCreate(
not_null<PeerData*> peer,
Fn<void(Link)> done,
bool revokeLegacyPermanent,
TimeId expireDate = 0,
int usageLimit = 0);
void requestJoinedFirstSlice(LinkKey key);
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(
LinkKey key) const;
const not_null<ApiWrap*> _api;
base::flat_map<not_null<PeerData*>, Links> _firstSlices;
base::flat_map<not_null<PeerData*>, mtpRequestId> _firstSliceRequests;
base::flat_map<LinkKey, JoinedByLinkSlice> _firstJoined;
base::flat_map<LinkKey, mtpRequestId> _firstJoinedRequests;
rpl::event_stream<LinkKey> _joinedFirstSliceLoaded;
base::flat_map<
not_null<PeerData*>,
std::vector<Fn<void(Link)>>> _createCallbacks;
base::flat_map<LinkKey, std::vector<Fn<void(Link)>>> _editCallbacks;
base::flat_map<LinkKey, std::vector<Fn<void()>>> _deleteCallbacks;
base::flat_map<
not_null<PeerData*>,
std::vector<Fn<void()>>> _deleteRevokedCallbacks;
rpl::event_stream<Update> _updates;
rpl::event_stream<not_null<PeerData*>> _allRevokedDestroyed;
};
} // namespace Api

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_sending.h"
#include "api/api_text_entities.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "data/data_document.h"
#include "data/data_photo.h"
@@ -76,7 +77,7 @@ void SendExistingMedia(
const auto newId = FullMsgId(
peerToChannel(peer->id),
session->data().nextLocalMessageId());
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
@@ -249,7 +250,7 @@ bool SendDice(Api::MessageToSend &message) {
const auto newId = FullMsgId(
peerToChannel(peer->id),
session->data().nextLocalMessageId());
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
auto &histories = history->owner().histories();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_authorizations.h"
#include "api/api_attached_stickers.h"
#include "api/api_hash.h"
#include "api/api_invite_links.h"
#include "api/api_media.h"
#include "api/api_sending.h"
#include "api/api_text_entities.h"
@@ -194,7 +195,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _attachedStickers(std::make_unique<Api::AttachedStickers>(this))
, _selfDestruct(std::make_unique<Api::SelfDestruct>(this))
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this)) {
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
crl::on_main(session, [=] {
// You can't use _session->lifetime() in the constructor,
// only queued, because it is not constructed yet.
@@ -354,7 +356,7 @@ void ApiWrap::requestTermsUpdate() {
const auto requestNext = [&](auto &&data) {
const auto timeout = (data.vexpires().v - base::unixtime::now());
_termsUpdateSendAt = crl::now() + snap(
_termsUpdateSendAt = crl::now() + std::clamp(
timeout * crl::time(1000),
kTermsUpdateTimeoutMin,
kTermsUpdateTimeoutMax);
@@ -1712,6 +1714,7 @@ void ApiWrap::kickParticipant(
not_null<ChatData*> chat,
not_null<UserData*> user) {
request(MTPmessages_DeleteChatUser(
MTP_flags(0),
chat->inputChat,
user->inputUser
)).done([=](const MTPUpdates &result) {
@@ -2120,33 +2123,6 @@ void ApiWrap::unblockPeer(not_null<PeerData*> peer, Fn<void()> onDone) {
_blockRequests.emplace(peer, requestId);
}
void ApiWrap::exportInviteLink(not_null<PeerData*> peer) {
if (_exportInviteRequests.find(peer) != end(_exportInviteRequests)) {
return;
}
const auto requestId = [&] {
return request(MTPmessages_ExportChatInvite(
peer->input
)).done([=](const MTPExportedChatInvite &result) {
_exportInviteRequests.erase(peer);
const auto link = (result.type() == mtpc_chatInviteExported)
? qs(result.c_chatInviteExported().vlink())
: QString();
if (const auto chat = peer->asChat()) {
chat->setInviteLink(link);
} else if (const auto channel = peer->asChannel()) {
channel->setInviteLink(link);
} else {
Unexpected("Peer in ApiWrap::exportInviteLink.");
}
}).fail([=](const RPCError &error) {
_exportInviteRequests.erase(peer);
}).send();
}();
_exportInviteRequests.emplace(peer, requestId);
}
void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
const auto key = [&] {
switch (peer.type()) {
@@ -2329,6 +2305,7 @@ void ApiWrap::clearHistory(not_null<PeerData*> peer, bool revoke) {
void ApiWrap::deleteConversation(not_null<PeerData*> peer, bool revoke) {
if (const auto chat = peer->asChat()) {
request(MTPmessages_DeleteChatUser(
MTP_flags(0),
chat->inputChat,
_session->user()->inputUser
)).done([=](const MTPUpdates &result) {
@@ -2373,14 +2350,14 @@ void ApiWrap::deleteHistory(
deleteTillId = history->lastMessage()->id;
}
if (const auto channel = peer->asChannel()) {
if (!justClear) {
if (!justClear && !revoke) {
channel->ptsWaitingForShortPoll(-1);
leaveChannel(channel);
} else {
if (const auto migrated = peer->migrateFrom()) {
deleteHistory(migrated, justClear, revoke);
}
if (IsServerMsgId(deleteTillId)) {
if (IsServerMsgId(deleteTillId) || (!justClear && revoke)) {
history->owner().histories().deleteAllMessages(
history,
deleteTillId,
@@ -2409,10 +2386,10 @@ void ApiWrap::applyUpdates(
}
int ApiWrap::applyAffectedHistory(
not_null<PeerData*> peer,
PeerData *peer,
const MTPmessages_AffectedHistory &result) {
const auto &data = result.c_messages_affectedHistory();
if (const auto channel = peer->asChannel()) {
if (const auto channel = peer ? peer->asChannel() : nullptr) {
channel->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v);
} else {
updates().updateAndApply(data.vpts().v, data.vpts_count().v);
@@ -4025,7 +4002,7 @@ void ApiWrap::forwardMessages(
ids.reserve(count);
randomIds.reserve(count);
for (const auto item : items) {
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
if (genClientSideMessage) {
if (const auto message = item->toHistoryMessage()) {
const auto newId = FullMsgId(
@@ -4348,7 +4325,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
auto newId = FullMsgId(
peerToChannel(peer->id),
_session->data().nextLocalMessageId());
auto randomId = rand_value<uint64>();
auto randomId = openssl::RandomValue<uint64>();
TextUtilities::Trim(sending);
@@ -4481,7 +4458,7 @@ void ApiWrap::sendBotStart(not_null<UserData*> bot, PeerData *chat) {
sendMessage(std::move(message));
return;
}
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
request(MTPmessages_StartBot(
bot->inputUser,
chat ? chat->input : MTP_inputPeerEmpty(),
@@ -4507,7 +4484,7 @@ void ApiWrap::sendInlineResult(
const auto newId = FullMsgId(
peerToChannel(peer->id),
_session->data().nextLocalMessageId());
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto clientFlags = NewMessageClientFlags();
@@ -4659,7 +4636,7 @@ void ApiWrap::sendMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
Api::SendOptions options) {
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
_session->data().registerMessageRandomId(randomId, item->fullId());
sendMediaWithRandomId(item, media, options, randomId);
@@ -4727,7 +4704,7 @@ void ApiWrap::sendAlbumWithUploaded(
const MessageGroupId &groupId,
const MTPInputMedia &media) {
const auto localId = item->fullId();
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
_session->data().registerMessageRandomId(randomId, localId);
const auto albumIt = _sendingAlbums.find(groupId.raw());
@@ -5240,6 +5217,10 @@ Api::GlobalPrivacy &ApiWrap::globalPrivacy() {
return *_globalPrivacy;
}
Api::InviteLinks &ApiWrap::inviteLinks() {
return *_inviteLinks;
}
void ApiWrap::createPoll(
const PollData &data,
const SendAction &action,
@@ -5275,7 +5256,7 @@ void ApiWrap::createPoll(
MTP_int(replyTo),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(rand_value<uint64>()),
MTP_long(openssl::RandomValue<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(action.options.scheduled)

View File

@@ -60,6 +60,7 @@ class AttachedStickers;
class SelfDestruct;
class SensitiveContent;
class GlobalPrivacy;
class InviteLinks;
namespace details {
@@ -153,7 +154,7 @@ public:
const MTPUpdates &updates,
uint64 sentMessageRandomId = 0);
int applyAffectedHistory(
not_null<PeerData*> peer,
PeerData *peer, // May be nullptr, like for deletePhoneCallHistory.
const MTPmessages_AffectedHistory &result);
void registerModifyRequest(const QString &key, mtpRequestId requestId);
@@ -289,7 +290,6 @@ public:
void blockPeer(not_null<PeerData*> peer);
void unblockPeer(not_null<PeerData*> peer, Fn<void()> onDone = nullptr);
void exportInviteLink(not_null<PeerData*> peer);
void requestNotifySettings(const MTPInputNotifyPeer &peer);
void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
void saveDraftToCloudDelayed(not_null<History*> history);
@@ -464,6 +464,7 @@ public:
[[nodiscard]] Api::SelfDestruct &selfDestruct();
[[nodiscard]] Api::SensitiveContent &sensitiveContent();
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
[[nodiscard]] Api::InviteLinks &inviteLinks();
void createPoll(
const PollData &data,
@@ -701,7 +702,6 @@ private:
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
base::flat_map<not_null<PeerData*>, mtpRequestId> _blockRequests;
base::flat_map<not_null<PeerData*>, mtpRequestId> _exportInviteRequests;
base::flat_map<PeerId, mtpRequestId> _notifySettingRequests;
base::flat_map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
@@ -828,6 +828,7 @@ private:
const std::unique_ptr<Api::SelfDestruct> _selfDestruct;
const std::unique_ptr<Api::SensitiveContent> _sensitiveContent;
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mtproto/sender.h"
#include "base/flat_set.h"
#include "base/openssl_help.h"
#include "boxes/confirm_box.h"
#include "boxes/confirm_phone_box.h" // ExtractPhonePrefix.
#include "boxes/photo_crop_box.h"
@@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwidget.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "main/main_session.h"
#include "facades.h"
#include "styles/style_layers.h"
@@ -382,7 +384,7 @@ void AddContactBox::save() {
lastName = QString();
}
_sentName = firstName;
_contactId = rand_value<uint64>();
_contactId = openssl::RandomValue<uint64>();
_addRequest = _session->api().request(MTPcontacts_ImportContacts(
MTP_vector<MTPInputContact>(
1,
@@ -612,7 +614,9 @@ void GroupInfoBox::createGroup(
}
void GroupInfoBox::submit() {
if (_creationRequestId) return;
if (_creationRequestId || _creatingInviteLink) {
return;
}
auto title = TextUtilities::PrepareForSending(_title->getLastText());
auto description = _description
@@ -651,6 +655,8 @@ void GroupInfoBox::submit() {
}
void GroupInfoBox::createChannel(const QString &title, const QString &description) {
Expects(!_creationRequestId);
const auto flags = (_type == Type::Megagroup)
? MTPchannels_CreateChannel::Flag::f_megagroup
: MTPchannels_CreateChannel::Flag::f_broadcast;
@@ -691,25 +697,7 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
std::move(image));
}
_createdChannel = channel;
_creationRequestId = _api.request(MTPmessages_ExportChatInvite(
_createdChannel->input
)).done([=](const MTPExportedChatInvite &result) {
_creationRequestId = 0;
if (result.type() == mtpc_chatInviteExported) {
auto link = qs(result.c_chatInviteExported().vlink());
_createdChannel->setInviteLink(link);
}
if (_channelDone) {
const auto callback = _channelDone;
const auto argument = _createdChannel;
closeBox();
callback(argument);
} else {
Ui::show(Box<SetupChannelBox>(
_navigation,
_createdChannel));
}
}).send();
checkInviteLink();
};
if (!success) {
LOG(("API Error: channel not found in updates (GroupInfoBox::creationDone)"));
@@ -728,6 +716,32 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
}).send();
}
void GroupInfoBox::checkInviteLink() {
Expects(_createdChannel != nullptr);
if (!_createdChannel->inviteLink().isEmpty()) {
channelReady();
return;
}
_creatingInviteLink = true;
_createdChannel->session().api().inviteLinks().create(
_createdChannel,
crl::guard(this, [=](auto&&) { channelReady(); }));
}
void GroupInfoBox::channelReady() {
if (_channelDone) {
const auto callback = _channelDone;
const auto argument = _createdChannel;
closeBox();
callback(argument);
} else {
Ui::show(Box<SetupChannelBox>(
_navigation,
_createdChannel));
}
}
void GroupInfoBox::descriptionResized() {
updateMaxHeight();
update();
@@ -829,7 +843,7 @@ void SetupChannelBox::prepare() {
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());
@@ -938,7 +952,7 @@ void SetupChannelBox::mouseMoveEvent(QMouseEvent *e) {
void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
if (_linkOver) {
if (_channel->inviteLink().isEmpty()) {
_channel->session().api().exportInviteLink(_channel);
_channel->session().api().inviteLinks().create(_channel);
} else {
QGuiApplication::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));

View File

@@ -121,6 +121,8 @@ private:
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
void submitName();
void submit();
void checkInviteLink();
void channelReady();
void descriptionResized();
void updateMaxHeight();
@@ -138,6 +140,7 @@ private:
// group / channel creation
mtpRequestId _creationRequestId = 0;
bool _creatingInviteLink = false;
ChannelData *_createdChannel = nullptr;
};

View File

@@ -905,3 +905,32 @@ pollResultsShowMore: SettingsButton(defaultSettingsButton) {
ripple: defaultRippleAnimation;
}
scheduleHeight: 95px;
scheduleDateTop: 38px;
scheduleDateField: InputField(defaultInputField) {
textMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
heightMin: 30px;
textAlign: align(top);
font: font(14px);
}
scheduleTimeField: InputField(scheduleDateField) {
border: 0px;
borderActive: 0px;
heightMin: 28px;
placeholderFont: font(14px);
placeholderFgActive: placeholderFgActive;
}
scheduleDateWidth: 136px;
scheduleTimeWidth: 72px;
scheduleAtSkip: 24px;
scheduleAtTop: 42px;
scheduleAtLabel: FlatLabel(defaultFlatLabel) {
}
scheduleTimeSeparator: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: font(14px);
}
}
scheduleTimeSeparatorPadding: margins(2px, 0px, 2px, 0px);

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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/widgets/checkbox.h"
@@ -393,7 +394,7 @@ void MaxInviteBox::prepare() {
_channel->session().changes().peerUpdates(
_channel,
Data::PeerUpdate::Flag::InviteLink
Data::PeerUpdate::Flag::InviteLinks
) | rpl::start_with_next([=] {
rtlupdate(_invitationLink);
}, lifetime());
@@ -407,7 +408,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
mouseMoveEvent(e);
if (_linkOver) {
if (_channel->inviteLink().isEmpty()) {
_channel->session().api().exportInviteLink(_channel);
_channel->session().api().inviteLinks().create(_channel);
} else {
QGuiApplication::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));
@@ -577,7 +578,8 @@ void DeleteMessagesBox::prepare() {
const auto appendDetails = [&](TextWithEntities &&text) {
details.append(qstr("\n\n")).append(std::move(text));
};
auto deleteText = tr::lng_box_delete();
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
*deleteText = tr::lng_box_delete();
auto deleteStyle = &st::defaultBoxButton;
if (const auto peer = _wipeHistoryPeer) {
if (_wipeHistoryJustClear) {
@@ -597,16 +599,22 @@ void DeleteMessagesBox::prepare() {
: peer->isMegagroup()
? tr::lng_sure_leave_group(tr::now)
: tr::lng_sure_leave_channel(tr::now);
deleteText = _wipeHistoryPeer->isUser()
? tr::lng_box_delete()
: tr::lng_box_leave();
deleteStyle = &(peer->isChannel()
? st::defaultBoxButton
: st::attentionBoxButton);
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);
@@ -643,7 +651,7 @@ void DeleteMessagesBox::prepare() {
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
addButton(
std::move(deleteText),
deleteText->value(),
[=] { deleteAndClear(); },
*deleteStyle);
addButton(tr::lng_cancel(), [=] { closeBox(); });
@@ -701,6 +709,8 @@ auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
tr::now,
lt_user,
user->firstName);
} else if (_wipeHistoryJustClear) {
return std::nullopt;
} else {
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
}
@@ -860,10 +870,16 @@ void DeleteMessagesBox::deleteAndClear() {
_deleteConfirmedCallback();
}
_session->data().histories().deleteMessages(_ids, revoke);
// deleteMessages can initiate closing of the current section,
// which will cause this box to be destroyed.
const auto session = _session;
Ui::hideLayer();
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

@@ -252,7 +252,7 @@ private:
QPointer<Ui::SlideWrap<>> _aboutSponsored;
QPointer<HostInput> _host;
QPointer<Ui::PortInput> _port;
QPointer<Ui::NumberInput> _port;
QPointer<Ui::InputField> _user;
QPointer<Ui::PasswordInput> _password;
QPointer<Base64UrlInput> _secret;
@@ -928,11 +928,12 @@ void ProxyBox::setupSocketAddress(const ProxyData &data) {
st::connectionHostInputField,
tr::lng_connection_host_ph(),
data.host);
_port = Ui::CreateChild<Ui::PortInput>(
_port = Ui::CreateChild<Ui::NumberInput>(
address,
st::connectionPortInputField,
tr::lng_connection_port_ph(),
data.port ? QString::number(data.port) : QString());
data.port ? QString::number(data.port) : QString(),
65535);
address->widthValue(
) | rpl::start_with_next([=](int width) {
_port->moveToRight(0, 0);

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unique_qptr.h"
#include "base/event_filter.h"
#include "base/call_delayed.h"
#include "base/openssl_help.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -885,7 +886,7 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
using namespace Settings;
const auto id = rand_value<uint64>();
const auto id = openssl::RandomValue<uint64>();
const auto error = lifetime().make_state<Errors>(Error::Question);
auto result = object_ptr<Ui::VerticalLayout>(this);

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_drag_area.h"
#include "history/history_item.h"
#include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
#include "platform/platform_specific.h"
#include "lang/lang_keys.h"
#include "media/streaming/media_streaming_instance.h"
@@ -171,7 +172,9 @@ EditCaptionBox::EditCaptionBox(
_thumbw = 0;
_thumbnailImageLoaded = true;
} else {
const auto thumbSize = st::msgFileThumbLayout.thumbSize;
const auto thumbSize = (!media->document()->isSongWithCover()
? st::msgFileThumbLayout
: st::msgFileLayout).thumbSize;
const auto tw = dimensions.width(), th = dimensions.height();
if (tw > th) {
_thumbw = (tw * thumbSize) / th;
@@ -183,19 +186,31 @@ EditCaptionBox::EditCaptionBox(
if (!image) {
return;
}
const auto options = Images::Option::Smooth
| Images::Option::RoundedSmall
| Images::Option::RoundedTopLeft
| Images::Option::RoundedTopRight
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
image->original(),
_thumbw * cIntRetinaFactor(),
0,
options,
thumbSize,
thumbSize));
if (media->document()->isSongWithCover()) {
const auto size = QSize(thumbSize, thumbSize);
_thumb = QPixmap(size);
_thumb.fill(Qt::transparent);
Painter p(&_thumb);
HistoryView::DrawThumbnailAsSongCover(
p,
_documentMedia,
QRect(QPoint(), size));
} else {
const auto options = Images::Option::Smooth
| Images::Option::RoundedSmall
| Images::Option::RoundedTopLeft
| Images::Option::RoundedTopRight
| Images::Option::RoundedBottomLeft
| Images::Option::RoundedBottomRight;
_thumb = App::pixmapFromImageInPlace(Images::prepare(
image->original(),
_thumbw * cIntRetinaFactor(),
0,
options,
thumbSize,
thumbSize));
}
_thumbnailImageLoaded = true;
};
_refreshThumbnail();
@@ -539,6 +554,14 @@ void EditCaptionBox::updateEditPreview() {
song->title,
song->performer);
_isAudio = true;
if (auto cover = song->cover; !cover.isNull()) {
_thumb = Ui::PrepareSongCoverForThumbnail(
cover,
st::msgFileLayout.thumbSize);
_thumbw = _thumb.width() / cIntRetinaFactor();
_thumbh = _thumb.height() / cIntRetinaFactor();
}
}
const auto getExt = [&] {
@@ -810,15 +833,21 @@ void EditCaptionBox::setupDragArea() {
areas.photo->setDroppedCallback(droppedCallback(true));
}
bool EditCaptionBox::isThumbedLayout() const {
return (_thumbw && !_isAudio);
}
void EditCaptionBox::updateBoxSize() {
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
if (_photo) {
newHeight += _wayWrap->height() / 2;
}
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
if (_photo || _animated) {
newHeight += std::max(_thumbh, _gifh);
} else if (_thumbw || _doc) {
} else if (isThumbedLayout() || _doc) {
newHeight += 0 + st.thumbSize + 0;
} else {
newHeight += st::boxTitleFont->height;
@@ -902,7 +931,9 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
icon->paintInCenter(p, inner);
}
} else if (_doc) {
const auto &st = _thumbw ? st::msgFileThumbLayout : st::msgFileLayout;
const auto &st = isThumbedLayout()
? st::msgFileThumbLayout
: st::msgFileLayout;
const auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
const auto h = 0 + st.thumbSize + 0;
const auto nameleft = 0 + st.thumbSize + st.padding.right();
@@ -918,18 +949,24 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
// Ui::FillRoundCorner(p, x, y, w, h, st::msgInBg, Ui::MessageInCorners, &st::msgInShadow);
const auto rthumb = style::rtlrect(x + 0, y + 0, st.thumbSize, st.thumbSize, width());
if (_thumbw) {
if (isThumbedLayout()) {
p.drawPixmap(rthumb.topLeft(), _thumb);
} else {
p.setPen(Qt::NoPen);
p.setBrush(st::msgFileInBg);
{
if (_isAudio && _thumbw) {
p.drawPixmap(rthumb.topLeft(), _thumb);
} else {
p.setBrush(st::msgFileInBg);
PainterHighQualityEnabler hq(p);
p.drawEllipse(rthumb);
}
const auto icon = &(_isAudio ? st::historyFileInPlay : _isImage ? st::historyFileInImage : st::historyFileInDocument);
const auto icon = &(_isAudio
? st::historyFileSongPlay
: _isImage
? st::historyFileInImage
: st::historyFileInDocument);
icon->paintInCenter(p, rthumb);
}
p.setFont(st::semiboldFont);

View File

@@ -96,6 +96,8 @@ private:
void createEditMediaButton();
bool setPreparedList(Ui::PreparedList &&list);
bool isThumbedLayout() const;
inline QString getNewMediaPath() {
return _preparedList.files.empty()
? QString()

View File

@@ -228,8 +228,8 @@ void EditColorBox::Picker::preparePaletteHSL() {
}
void EditColorBox::Picker::updateCurrentPoint(QPoint localPosition) {
auto x = snap(localPosition.x(), 0, width()) / float64(width());
auto y = snap(localPosition.y(), 0, height()) / float64(height());
auto x = std::clamp(localPosition.x(), 0, width()) / float64(width());
auto y = std::clamp(localPosition.y(), 0, height()) / float64(height());
if (_x != x || _y != y) {
_x = x;
_y = y;
@@ -245,14 +245,14 @@ void EditColorBox::Picker::setHSB(HSB hsb) {
_topright = _topright.toRgb();
_bottomleft = _bottomright = QColor(0, 0, 0);
_x = snap(hsb.saturation / 255., 0., 1.);
_y = 1. - snap(hsb.brightness / 255., 0., 1.);
_x = std::clamp(hsb.saturation / 255., 0., 1.);
_y = 1. - std::clamp(hsb.brightness / 255., 0., 1.);
} else {
_topleft = _topright = QColor::fromHsl(0, 255, hsb.brightness);
_bottomleft = _bottomright = QColor::fromHsl(0, 0, hsb.brightness);
_x = snap(hsb.hue / 360., 0., 1.);
_y = 1. - snap(hsb.saturation / 255., 0., 1.);
_x = std::clamp(hsb.hue / 360., 0., 1.);
_y = 1. - std::clamp(hsb.saturation / 255., 0., 1.);
}
_paletteInvalidated = true;
@@ -291,7 +291,7 @@ public:
return _value;
}
void setValue(float64 value) {
_value = snap(value, 0., 1.);
_value = std::clamp(value, 0., 1.);
update();
}
void setHSB(HSB hsb);
@@ -508,12 +508,12 @@ float64 EditColorBox::Slider::valueFromColor(QColor color) const {
}
float64 EditColorBox::Slider::valueFromHue(int hue) const {
return (1. - snap(hue, 0, 360) / 360.);
return (1. - std::clamp(hue, 0, 360) / 360.);
}
void EditColorBox::Slider::setAlpha(int alpha) {
if (_type == Type::Opacity) {
_value = snap(alpha, 0, 255) / 255.;
_value = std::clamp(alpha, 0, 255) / 255.;
update();
}
}
@@ -534,7 +534,7 @@ void EditColorBox::Slider::updatePixmapFromMask() {
void EditColorBox::Slider::updateCurrentPoint(QPoint localPosition) {
auto coord = (isHorizontal() ? localPosition.x() : localPosition.y()) - st::colorSliderSkip;
auto maximum = (isHorizontal() ? width() : height()) - 2 * st::colorSliderSkip;
auto value = snap(coord, 0, maximum) / float64(maximum);
auto value = std::clamp(coord, 0, maximum) / float64(maximum);
if (_value != value) {
_value = value;
update();
@@ -663,7 +663,7 @@ void EditColorBox::Field::wheelEvent(QWheelEvent *e) {
void EditColorBox::Field::changeValue(int delta) {
auto currentValue = value();
auto newValue = snap(currentValue + delta, 0, _limit);
auto newValue = std::clamp(currentValue + delta, 0, _limit);
if (newValue != currentValue) {
setText(QString::number(newValue));
setFocus();

View File

@@ -66,10 +66,10 @@ private:
[[nodiscard]] QColor applyLimits(QColor color) const;
int percentFromByte(int byte) {
return snap(qRound(byte * 100 / 255.), 0, 100);
return std::clamp(qRound(byte * 100 / 255.), 0, 100);
}
int percentToByte(int percent) {
return snap(qRound(percent * 255 / 100.), 0, 255);
return std::clamp(qRound(percent * 255 / 100.), 0, 255);
}
class Picker;

View File

@@ -62,22 +62,6 @@ public:
};
class TypeDelegate final : public PeerListContentDelegate {
public:
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override;
void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override;
};
class TypeController final : public PeerListController {
public:
TypeController(
@@ -194,39 +178,6 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback() {
};
}
void TypeDelegate::peerListSetTitle(rpl::producer<QString> title) {
}
void TypeDelegate::peerListSetAdditionalTitle(rpl::producer<QString> title) {
}
bool TypeDelegate::peerListIsRowChecked(not_null<PeerListRow*> row) {
return false;
}
int TypeDelegate::peerListSelectedRowsCount() {
return 0;
}
void TypeDelegate::peerListScrollToTop() {
}
void TypeDelegate::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
Unexpected("Item selection in Info::Profile::Members.");
}
void TypeDelegate::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
Unexpected("Item selection in Info::Profile::Members.");
}
void TypeDelegate::peerListFinishSelectedRowsBunch() {
}
void TypeDelegate::peerListSetDescription(
object_ptr<Ui::FlatLabel> description) {
description.destroy();
}
TypeController::TypeController(
not_null<Main::Session*> session,
Flags options,
@@ -412,7 +363,9 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::membersMarginTop));
const auto delegate = container->lifetime().make_state<TypeDelegate>();
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<TypeController>(
&session(),
_options,

View File

@@ -1440,7 +1440,10 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
}
// Snap the index.
newSelectedIndex = snap(newSelectedIndex, firstEnabled - 1, lastEnabled);
newSelectedIndex = std::clamp(
newSelectedIndex,
firstEnabled - 1,
lastEnabled);
// Skip the disabled rows.
if (newSelectedIndex < firstEnabled) {

View File

@@ -302,7 +302,7 @@ public:
virtual void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) = 0;
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) = 0;
virtual int peerListSelectedRowsCount() = 0;
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
virtual void peerListRestoreState(
@@ -375,6 +375,9 @@ public:
_delegate = delegate;
prepare();
}
[[nodiscard]] not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
void setStyleOverrides(
const style::PeerList *listSt,
@@ -453,9 +456,6 @@ public:
virtual ~PeerListController() = default;
protected:
not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
PeerListSearchController *searchController() const {
return _searchController.get();
}
@@ -837,7 +837,7 @@ public:
}
void peerListShowRowMenu(
not_null<PeerListRow*> row,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) override {
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override {
_content->showRowMenu(row, std::move(destroyed));
}
@@ -851,6 +851,38 @@ private:
};
class PeerListContentDelegateSimple : public PeerListContentDelegate {
public:
void peerListSetTitle(rpl::producer<QString> title) override {
}
void peerListSetAdditionalTitle(rpl::producer<QString> title) override {
}
bool peerListIsRowChecked(not_null<PeerListRow*> row) override {
return false;
}
int peerListSelectedRowsCount() override {
return 0;
}
void peerListScrollToTop() override {
}
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override {
Unexpected("...DelegateSimple::peerListAddSelectedPeerInBunch");
}
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
Unexpected("...DelegateSimple::peerListAddSelectedRowInBunch");
}
void peerListFinishSelectedRowsBunch() override {
Unexpected("...DelegateSimple::peerListFinishSelectedRowsBunch");
}
void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override {
description.destroy();
}
};
class PeerListBox
: public Ui::BoxContent
, public PeerListContentDelegate {

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peer_list_controllers.h"
#include "base/openssl_help.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/ui_utility.h"
@@ -23,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "history/history.h"
#include "dialogs/dialogs_main_list.h"
#include "window/window_session_controller.h" // onShowAddContact()
#include "window/window_session_controller.h" // showAddContact()
#include "facades.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
@@ -35,7 +36,7 @@ void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
const auto randomId = rand_value<uint64>();
const auto randomId = openssl::RandomValue<uint64>();
const auto api = &chat->session().api();
history->sendRequestId = api->request(MTPmessages_SendMedia(
MTP_flags(0),
@@ -112,7 +113,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
box->addLeftButton(
tr::lng_profile_add_contact(),
[=] { controller->widget()->onShowAddContact(); });
[=] { controller->widget()->showAddContact(); });
};
return Box<PeerListBox>(
std::make_unique<ContactsBoxController>(

View File

@@ -20,10 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/boxes/calendar_box.h"
#include "ui/special_buttons.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "settings/settings_privacy_security.h"
#include "boxes/calendar_box.h"
#include "boxes/confirm_box.h"
#include "boxes/passcode_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
@@ -691,7 +691,7 @@ void EditRestrictedBox::showRestrictUntil() {
: base::unixtime::parse(getRealUntilValue()).date();
auto month = highlighted;
_restrictUntilBox = Ui::show(
Box<CalendarBox>(
Box<Ui::CalendarBox>(
month,
highlighted,
[this](const QDate &date) {

View File

@@ -18,6 +18,7 @@ class LinkButton;
class Checkbox;
class Radiobutton;
class RadiobuttonGroup;
class CalendarBox;
template <typename Widget>
class SlideWrap;
} // namespace Ui
@@ -26,7 +27,6 @@ namespace Core {
struct CloudPasswordResult;
} // namespace Core
class CalendarBox;
class PasscodeBox;
class EditParticipantBox : public Ui::BoxContent {
@@ -162,7 +162,7 @@ private:
std::shared_ptr<Ui::RadiobuttonGroup> _untilGroup;
std::vector<base::unique_qptr<Ui::Radiobutton>> _untilVariants;
QPointer<CalendarBox> _restrictUntilBox;
QPointer<Ui::CalendarBox> _restrictUntilBox;
static constexpr auto kUntilOneDay = -1;
static constexpr auto kUntilOneWeek = -2;

View File

@@ -174,6 +174,7 @@ void SaveChatParticipantKick(
Fn<void()> onDone,
Fn<void()> onFail) {
chat->session().api().request(MTPmessages_DeleteChatUser(
MTP_flags(0),
chat->inputChat,
user->inputUser
)).done([=](const MTPUpdates &result) {
@@ -358,7 +359,7 @@ bool ParticipantsAdditionalData::canRemoveUser(
if (canRestrictUser(user)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return chat->invitedByMe.contains(user);
return !user->isSelf() && chat->invitedByMe.contains(user);
}
return false;
}
@@ -1212,6 +1213,9 @@ void ParticipantsBoxController::rebuildChatAdmins(
return true;
}();
if (same) {
if (!_allLoaded && !delegate()->peerListFullRowsCount()) {
chatListReady();
}
return;
}

View File

@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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/stickers_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
@@ -45,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "info/profile/info_profile_icon.h"
#include "app.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "facades.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -236,6 +240,49 @@ void ShowEditPermissions(
}, box->lifetime());
}
void ShowEditInviteLinks(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
const auto box = Ui::show(
Box<EditPeerPermissionsBox>(navigation, peer),
Ui::LayerOption::KeepOther);
const auto saving = box->lifetime().make_state<int>(0);
const auto save = [=](
not_null<PeerData*> peer,
EditPeerPermissionsBox::Result result) {
Expects(result.slowmodeSeconds == 0 || peer->isChannel());
const auto close = crl::guard(box, [=] { box->closeBox(); });
SaveDefaultRestrictions(
peer,
MTP_chatBannedRights(MTP_flags(result.rights), MTP_int(0)),
close);
if (const auto channel = peer->asChannel()) {
SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
}
};
box->saveEvents(
) | rpl::start_with_next([=](EditPeerPermissionsBox::Result result) {
if (*saving) {
return;
}
*saving = true;
const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat();
if (!result.slowmodeSeconds || !chat) {
save(saveFor, result);
return;
}
const auto api = &peer->session().api();
api->migrateChat(chat, [=](not_null<ChannelData*> channel) {
save(channel, result);
}, [=](const RPCError &error) {
*saving = false;
});
}, box->lifetime());
}
} // namespace
namespace {
@@ -285,7 +332,6 @@ private:
void showEditLinkedChatBox();
void fillPrivacyTypeButton();
void fillLinkedChatButton();
void fillInviteLinkButton();
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
@@ -594,6 +640,8 @@ void Controller::refreshHistoryVisibility(anim::type animated) {
void Controller::showEditPeerTypeBox(
std::optional<rpl::producer<QString>> error) {
Expects(_privacySavedValue.has_value());
const auto boxCallback = crl::guard(this, [=](
Privacy checked, QString publicLink) {
_privacyTypeUpdates.fire(std::move(checked));
@@ -606,7 +654,7 @@ void Controller::showEditPeerTypeBox(
_peer,
_channelHasLocationOriginalValue,
boxCallback,
_privacySavedValue,
*_privacySavedValue,
_usernameSavedValue,
error),
Ui::LayerOption::KeepOther);
@@ -752,22 +800,9 @@ void Controller::fillLinkedChatButton() {
_linkedChatUpdates.fire_copy(*_linkedChatSavedValue);
}
void Controller::fillInviteLinkButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto buttonCallback = [=] {
Ui::show(Box<EditPeerTypeBox>(_peer), Ui::LayerOption::KeepOther);
};
AddButtonWithText(
_controls.buttonsLayout,
tr::lng_profile_invite_link_section(),
rpl::single(QString()), //Empty text.
buttonCallback);
}
void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto channel = _peer->asChannel();
if (!channel) return;
@@ -868,6 +903,11 @@ void Controller::fillManageSection() {
? channel->canEditPermissions()
: chat->canEditPermissions();
}();
const auto canEditInviteLinks = [&] {
return isChannel
? channel->canHaveInviteLink()
: chat->canHaveInviteLink();
}();
const auto canViewAdmins = [&] {
return isChannel
? channel->canViewAdmins()
@@ -913,8 +953,6 @@ void Controller::fillManageSection() {
if (canEditUsername) {
fillPrivacyTypeButton();
} else if (canEditInviteLink) {
fillInviteLinkButton();
}
if (canViewOrEditLinkedChat) {
fillLinkedChatButton();
@@ -949,6 +987,29 @@ void Controller::fillManageSection() {
[=] { ShowEditPermissions(_navigation, _peer); },
st::infoIconPermissions);
}
//if (canEditInviteLinks) { // #TODO links
// AddButtonWithCount(
// _controls.buttonsLayout,
// tr::lng_manage_peer_invite_links(),
// Info::Profile::MigratedOrMeValue(
// _peer
// ) | rpl::map([=](not_null<PeerData*> peer) {
// peer->session().api().inviteLinks().requestLinks(peer);
// return peer->session().changes().peerUpdates(
// peer,
// Data::PeerUpdate::Flag::InviteLinks
// ) | rpl::map([=] {
// return peer->session().api().inviteLinks().links(
// peer).count;
// });
// }) | rpl::flatten_latest(
// ) | ToPositiveNumberString(),
// [=] { Ui::show(
// Box(ManageInviteLinksBox, _peer),
// Ui::LayerOption::KeepOther);
// },
// st::infoIconInviteLinks);
//}
if (canViewAdmins) {
AddButtonWithCount(
_controls.buttonsLayout,

View File

@@ -0,0 +1,591 @@
/*
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_invite_link.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_histories.h"
#include "main/main_session.h"
#include "api/api_invite_links.h"
#include "base/unixtime.h"
#include "apiwrap.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/popup_menu.h"
#include "ui/abstract_button.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "boxes/share_box.h"
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
#include "history/history_message.h" // GetErrorTextForSending.
#include "history/history.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_box.h"
#include "mainwindow.h"
#include "facades.h" // Ui::showPerProfile.
#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "mtproto/sender.h"
#include "styles/style_info.h"
#include <QtGui/QGuiApplication>
namespace {
constexpr auto kFirstPage = 20;
constexpr auto kPerPage = 100;
using LinkData = Api::InviteLink;
class Controller final : public PeerListController {
public:
Controller(not_null<PeerData*> peer, const LinkData &data);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
Main::Session &session() const override;
private:
void appendSlice(const Api::JoinedByLinkSlice &slice);
[[nodiscard]] object_ptr<Ui::RpWidget> prepareHeader();
const not_null<PeerData*> _peer;
LinkData _data;
mtpRequestId _requestId = 0;
std::optional<Api::JoinedByLinkUser> _lastUser;
bool _allLoaded = false;
MTP::Sender _api;
rpl::lifetime _lifetime;
};
class SingleRowController final : public PeerListController {
public:
SingleRowController(not_null<PeerData*> peer, TimeId date);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
Main::Session &session() const override;
private:
const not_null<PeerData*> _peer;
TimeId _date = 0;
};
void AddHeaderBlock(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
const LinkData &data,
TimeId now) {
const auto link = data.link;
const auto weak = Ui::MakeWeak(container);
const auto copyLink = crl::guard(weak, [=] {
CopyInviteLink(link);
});
const auto shareLink = crl::guard(weak, [=] {
ShareInviteLinkBox(peer, link);
});
const auto revokeLink = crl::guard(weak, [=] {
RevokeLink(peer, link);
});
const auto createMenu = [=] {
auto result = base::make_unique_q<Ui::PopupMenu>(container);
result->addAction(
tr::lng_group_invite_context_copy(tr::now),
copyLink);
result->addAction(
tr::lng_group_invite_context_share(tr::now),
shareLink);
result->addAction(
tr::lng_group_invite_context_revoke(tr::now),
revokeLink);
return result;
};
const auto prefix = qstr("https://");
const auto label = container->lifetime().make_state<Ui::InviteLinkLabel>(
container,
rpl::single(link.startsWith(prefix)
? link.mid(prefix.size())
: link),
createMenu);
container->add(
label->take(),
st::inviteLinkFieldPadding);
label->clicks(
) | rpl::start_with_next(copyLink, label->lifetime());
if ((data.expireDate <= 0 || now < data.expireDate)
&& (data.usageLimit <= 0 || data.usage < data.usageLimit)) {
AddCopyShareLinkButtons(
container,
copyLink,
shareLink);
}
}
void AddHeader(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
const LinkData &data) {
using namespace Settings;
if (!data.revoked && !data.permanent) {
const auto now = base::unixtime::now();
AddHeaderBlock(container, peer, data, now);
AddSkip(container, st::inviteLinkJoinedRowPadding.bottom() * 2);
if (data.expireDate > 0) {
AddDividerText(
container,
(data.expireDate > now
? tr::lng_group_invite_expires_at(
lt_when,
rpl::single(langDateTime(
base::unixtime::parse(data.expireDate))))
: tr::lng_group_invite_expired_already()));
} else {
AddDivider(container);
}
}
AddSkip(container);
AddSubsectionTitle(
container,
tr::lng_group_invite_created_by());
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<
SingleRowController
>(data.admin, data.date);
const auto content = container->add(object_ptr<PeerListContent>(
container,
controller));
delegate->setContent(content);
controller->setDelegate(delegate);
}
Controller::Controller(not_null<PeerData*> peer, const LinkData &data)
: _peer(peer)
, _data(data)
, _api(&_peer->session().api().instance()) {
}
object_ptr<Ui::RpWidget> Controller::prepareHeader() {
using namespace Settings;
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data();
AddHeader(container, _peer, _data);
AddDivider(container);
AddSkip(container);
AddSubsectionTitle(
container,
(_data.usage
? tr::lng_group_invite_joined(
lt_count,
rpl::single(float64(_data.usage)))
: tr::lng_group_invite_no_joined()));
return result;
}
void Controller::prepare() {
delegate()->peerListSetAboveWidget(prepareHeader());
_allLoaded = (_data.usage == 0);
const auto &inviteLinks = _peer->session().api().inviteLinks();
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _data.link);
if (slice) {
appendSlice(*slice);
}
loadMoreRows();
}
void Controller::loadMoreRows() {
if (_requestId || _allLoaded) {
return;
}
_allLoaded = true; // #TODO links
//_requestId = _api.request(MTPmessages_GetChatInviteImporters(
// _peer->input,
// MTP_string(_data.link),
// MTP_int(_lastUser ? _lastUser->date : 0),
// _lastUser ? _lastUser->user->inputUser : MTP_inputUserEmpty(),
// MTP_int(_lastUser ? kPerPage : kFirstPage)
//)).done([=](const MTPmessages_ChatInviteImporters &result) {
// _requestId = 0;
// auto slice = Api::ParseJoinedByLinkSlice(_peer, result);
// _allLoaded = slice.users.empty();
// appendSlice(slice);
//}).fail([=](const RPCError &error) {
// _requestId = 0;
// _allLoaded = true;
//}).send();
}
void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
for (const auto &user : slice.users) {
_lastUser = user;
delegate()->peerListAppendRow(
std::make_unique<PeerListRow>(user.user));
}
delegate()->peerListRefreshRows();
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::showPeerProfile(row->peer());
}
Main::Session &Controller::session() const {
return _peer->session();
}
SingleRowController::SingleRowController(
not_null<PeerData*> peer,
TimeId date)
: _peer(peer)
, _date(date) {
}
void SingleRowController::prepare() {
auto row = std::make_unique<PeerListRow>(_peer);
row->setCustomStatus(langDateTime(base::unixtime::parse(_date)));
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
}
void SingleRowController::loadMoreRows() {
}
void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
Ui::showPeerProfile(row->peer());
}
Main::Session &SingleRowController::session() const {
return _peer->session();
}
} // namespace
void AddPermanentLinkBlock(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer) {
const auto computePermanentLink = [=] {
const auto &links = peer->session().api().inviteLinks().links(
peer).links;
const auto link = links.empty() ? nullptr : &links.front();
return (link && link->permanent && !link->revoked) ? link : nullptr;
};
auto value = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::InviteLinks
) | rpl::map([=] {
const auto link = computePermanentLink();
return link
? std::make_tuple(link->link, link->usage)
: std::make_tuple(QString(), 0);
}) | rpl::distinct_until_changed(
) | rpl::start_spawning(container->lifetime());
const auto weak = Ui::MakeWeak(container);
const auto copyLink = crl::guard(weak, [=] {
if (const auto link = computePermanentLink()) {
CopyInviteLink(link->link);
}
});
const auto shareLink = crl::guard(weak, [=] {
if (const auto link = computePermanentLink()) {
ShareInviteLinkBox(peer, link->link);
}
});
const auto revokeLink = crl::guard(weak, [=] {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto done = crl::guard(weak, [=] {
const auto close = [=](auto&&) {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().revokePermanent(peer, close);
});
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_about_new(tr::now), done),
Ui::LayerOption::KeepOther);
});
auto link = rpl::duplicate(
value
) | rpl::map([=](QString link, int usage) {
const auto prefix = qstr("https://");
return link.startsWith(prefix) ? link.mid(prefix.size()) : link;
});
const auto createMenu = [=] {
auto result = base::make_unique_q<Ui::PopupMenu>(container);
result->addAction(
tr::lng_group_invite_context_copy(tr::now),
copyLink);
result->addAction(
tr::lng_group_invite_context_share(tr::now),
shareLink);
result->addAction(
tr::lng_group_invite_context_revoke(tr::now),
revokeLink);
return result;
};
const auto label = container->lifetime().make_state<Ui::InviteLinkLabel>(
container,
std::move(link),
createMenu);
container->add(
label->take(),
st::inviteLinkFieldPadding);
label->clicks(
) | rpl::start_with_next(copyLink, label->lifetime());
AddCopyShareLinkButtons(
container,
copyLink,
shareLink);
struct JoinedState {
QImage cachedUserpics;
std::vector<HistoryView::UserpicInRow> list;
int count = 0;
bool allUserpicsLoaded = false;
rpl::variable<Ui::JoinedCountContent> content;
rpl::lifetime lifetime;
};
const auto state = container->lifetime().make_state<JoinedState>();
const auto push = [=] {
HistoryView::GenerateUserpicsInRow(
state->cachedUserpics,
state->list,
st::inviteLinkUserpics,
0);
state->allUserpicsLoaded = ranges::all_of(
state->list,
[](const HistoryView::UserpicInRow &element) {
return !element.peer->hasUserpic() || element.view->image();
});
state->content = Ui::JoinedCountContent{
.count = state->count,
.userpics = state->cachedUserpics
};
};
std::move(
value
) | rpl::map([=](QString link, int usage) {
return peer->session().api().inviteLinks().joinedFirstSliceValue(
peer,
link,
usage);
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](const Api::JoinedByLinkSlice &slice) {
auto list = std::vector<HistoryView::UserpicInRow>();
list.reserve(slice.users.size());
for (const auto &item : slice.users) {
const auto i = ranges::find(
state->list,
item.user,
&HistoryView::UserpicInRow::peer);
if (i != end(state->list)) {
list.push_back(std::move(*i));
} else {
list.push_back({ item.user });
}
}
state->count = slice.count;
state->list = std::move(list);
push();
}, state->lifetime);
peer->session().downloaderTaskFinished(
) | rpl::filter([=] {
return !state->allUserpicsLoaded;
}) | rpl::start_with_next([=] {
auto pushing = false;
state->allUserpicsLoaded = true;
for (const auto &element : state->list) {
if (!element.peer->hasUserpic()) {
continue;
} else if (element.peer->userpicUniqueKey(element.view)
!= element.uniqueKey) {
pushing = true;
} else if (!element.view->image()) {
state->allUserpicsLoaded = false;
}
}
if (pushing) {
push();
}
}, state->lifetime);
Ui::AddJoinedCountButton(
container,
state->content.value(),
st::inviteLinkJoinedRowPadding
)->setClickedCallback([=] {
});
container->add(object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>(
container,
object_ptr<Ui::FixedHeightWidget>(
container,
st::inviteLinkJoinedRowPadding.bottom()))
)->setDuration(0)->toggleOn(state->content.value(
) | rpl::map([=](const Ui::JoinedCountContent &content) {
return (content.count <= 0);
}));
}
void CopyInviteLink(const QString &link) {
QGuiApplication::clipboard()->setText(link);
Ui::Toast::Show(tr::lng_group_invite_copied(tr::now));
}
void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
const auto session = &peer->session();
const auto sending = std::make_shared<bool>();
const auto box = std::make_shared<QPointer<ShareBox>>();
auto copyCallback = [=] {
QGuiApplication::clipboard()->setText(link);
Ui::Toast::Show(tr::lng_group_invite_copied(tr::now));
};
auto submitCallback = [=](
std::vector<not_null<PeerData*>> &&result,
TextWithTags &&comment,
Api::SendOptions options) {
if (*sending || result.empty()) {
return;
}
const auto error = [&] {
for (const auto peer : result) {
const auto error = GetErrorTextForSending(
peer,
{},
comment);
if (!error.isEmpty()) {
return std::make_pair(error, peer);
}
}
return std::make_pair(QString(), result.front());
}();
if (!error.first.isEmpty()) {
auto text = TextWithEntities();
if (result.size() > 1) {
text.append(
Ui::Text::Bold(error.second->name)
).append("\n\n");
}
text.append(error.first);
Ui::show(
Box<InformBox>(text),
Ui::LayerOption::KeepOther);
return;
}
*sending = true;
if (!comment.text.isEmpty()) {
comment.text = link + "\n" + comment.text;
const auto add = link.size() + 1;
for (auto &tag : comment.tags) {
tag.offset += add;
}
}
const auto owner = &peer->owner();
auto &api = peer->session().api();
auto &histories = owner->histories();
const auto requestType = Data::Histories::RequestType::Send;
for (const auto peer : result) {
const auto history = owner->history(peer);
auto message = ApiWrap::MessageToSend(history);
message.textWithTags = comment;
message.action.options = options;
message.action.clearDraft = false;
api.sendMessage(std::move(message));
}
Ui::Toast::Show(tr::lng_share_done(tr::now));
if (*box) {
(*box)->closeBox();
}
};
auto filterCallback = [](PeerData *peer) {
return peer->canWrite();
};
*box = Ui::show(
Box<ShareBox>(
App::wnd()->sessionController(),
std::move(copyCallback),
std::move(submitCallback),
std::move(filterCallback)),
Ui::LayerOption::KeepOther);
}
void RevokeLink(not_null<PeerData*> peer, const QString &link) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto revoke = [=] {
const auto done = [=](const LinkData &data) {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().revoke(peer, link, done);
};
*box = Ui::show(
Box<ConfirmBox>(
tr::lng_group_invite_revoke_about(tr::now),
revoke),
Ui::LayerOption::KeepOther);
}
void ShowInviteLinkBox(
not_null<PeerData*> peer,
const Api::InviteLink &link) {
auto initBox = [=](not_null<Ui::BoxContent*> box) {
box->setTitle((link.permanent && !link.revoked)
? tr::lng_manage_peer_link_permanent()
: tr::lng_manage_peer_link_invite());
peer->session().api().inviteLinks().updates(
peer
) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) {
if (update.was == link.link
&& (!update.now || (!link.revoked && update.now->revoked))) {
box->closeBox();
}
}, box->lifetime());
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
};
if (link.usage > 0) {
Ui::show(
Box<PeerListBox>(
std::make_unique<Controller>(peer, link),
std::move(initBox)),
Ui::LayerOption::KeepOther);
} else {
Ui::show(Box([=](not_null<Ui::GenericBox*> box) {
initBox(box);
const auto container = box->verticalLayout();
AddHeader(container, peer, link);
}), Ui::LayerOption::KeepOther);
}
}

View File

@@ -0,0 +1,32 @@
/*
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"
class PeerData;
namespace Api {
struct InviteLink;
} // namespace Api
namespace Ui {
class VerticalLayout;
} // namespace Ui
void AddPermanentLinkBlock(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer);
void CopyInviteLink(const QString &link);
void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link);
void RevokeLink(not_null<PeerData*> peer, const QString &link);
void ShowInviteLinkBox(
not_null<PeerData*> peer,
const Api::InviteLink &link);

View File

@@ -0,0 +1,776 @@
/*
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_invite_links.h"
#include "data/data_peer.h"
#include "main/main_session.h"
#include "api/api_invite_links.h"
#include "ui/boxes/edit_invite_link.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_peer_invite_link.h"
#include "settings/settings_common.h" // AddDivider.
#include "apiwrap.h"
#include "base/weak_ptr.h"
#include "base/unixtime.h"
#include "styles/style_info.h"
#include "styles/style_layers.h" // st::boxDividerLabel
#include "styles/style_settings.h" // st::settingsDividerLabelPadding
#include <xxhash.h>
namespace {
constexpr auto kPreloadPages = 2;
constexpr auto kFullArcLength = 360 * 16;
enum class Color {
Permanent,
Expiring,
ExpireSoon,
Expired,
Revoked,
Count,
};
using InviteLinkData = Api::InviteLink;
using InviteLinksSlice = Api::PeerInviteLinks;
struct InviteLinkAction {
enum class Type {
Copy,
Share,
Edit,
Revoke,
Delete,
};
QString link;
Type type = Type::Copy;
};
class Row;
class RowDelegate {
public:
virtual void rowUpdateRow(not_null<Row*> row) = 0;
virtual void rowPaintIcon(
QPainter &p,
int x,
int y,
int size,
float64 progress,
Color color) = 0;
};
class Row final : public PeerListRow {
public:
Row(
not_null<RowDelegate*> delegate,
const InviteLinkData &data,
TimeId now);
void update(const InviteLinkData &data, TimeId now);
void updateExpireProgress(TimeId now);
[[nodiscard]] InviteLinkData data() const;
[[nodiscard]] crl::time updateExpireIn() const;
QString generateName() override;
QString generateShortName() override;
PaintRoundImageCallback generatePaintUserpicCallback() override;
QSize actionSize() const override;
QMargins actionMargins() const override;
void paintAction(
Painter &p,
int x,
int y,
int outerWidth,
bool selected,
bool actionSelected) override;
private:
const not_null<RowDelegate*> _delegate;
InviteLinkData _data;
QString _status;
float64 _progressTillExpire = 0.;
Color _color = Color::Permanent;
};
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
}
[[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) {
return ComputeRowId(data.link);
}
[[nodiscard]] float64 ComputeProgress(
const InviteLinkData &link,
TimeId now) {
const auto startDate = link.startDate ? link.startDate : link.date;
if (link.expireDate <= startDate && link.usageLimit <= 0) {
return -1;
}
const auto expireProgress = (link.expireDate <= startDate
|| now <= startDate)
? 0.
: (link.expireDate <= now)
? 1.
: (now - startDate) / float64(link.expireDate - startDate);
const auto usageProgress = (link.usageLimit <= 0 || link.usage <= 0)
? 0.
: (link.usageLimit <= link.usage)
? 1.
: link.usage / float64(link.usageLimit);
return std::max(expireProgress, usageProgress);
}
[[nodiscard]] Color ComputeColor(
const InviteLinkData &link,
float64 progress) {
return link.revoked
? Color::Revoked
: (progress >= 1.)
? Color::Expired
: (progress >= 3 / 4.)
? Color::ExpireSoon
: (progress >= 0.)
? Color::Expiring
: Color::Permanent;
}
[[nodiscard]] QString ComputeStatus(const InviteLinkData &link, TimeId now) {
auto result = link.usage
? tr::lng_group_invite_joined(tr::now, lt_count_decimal, link.usage)
: tr::lng_group_invite_no_joined(tr::now);
const auto add = [&](const QString &text) {
result += QString::fromUtf8(" \xE2\xB8\xB1 ") + text;
};
if (link.revoked) {
add(tr::lng_group_invite_link_revoked(tr::now));
} else if ((link.usageLimit > 0 && link.usage >= link.usageLimit)
|| (link.expireDate > 0 && now >= link.expireDate)) {
add(tr::lng_group_invite_link_expired(tr::now));
}
return result;
}
void EditLink(not_null<PeerData*> peer, const InviteLinkData &data) {
const auto creating = data.link.isEmpty();
const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
using Fields = Ui::InviteLinkFields;
const auto done = [=](Fields result) {
const auto finish = [=](Api::InviteLink finished) {
if (creating) {
ShowInviteLinkBox(peer, finished);
}
if (*box) {
(*box)->closeBox();
}
};
if (creating) {
peer->session().api().inviteLinks().create(
peer,
finish,
result.expireDate,
result.usageLimit);
} else {
peer->session().api().inviteLinks().edit(
peer,
result.link,
result.expireDate,
result.usageLimit,
finish);
}
};
*box = Ui::show(
(creating
? Box(Ui::CreateInviteLinkBox, done)
: Box(
Ui::EditInviteLinkBox,
Fields{
.link = data.link,
.expireDate = data.expireDate,
.usageLimit = data.usageLimit
},
done)),
Ui::LayerOption::KeepOther);
}
void DeleteLink(not_null<PeerData*> peer, const QString &link) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().destroy(peer, link, finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
Ui::LayerOption::KeepOther);
}
void DeleteAllRevoked(not_null<PeerData*> peer) {
const auto box = std::make_shared<QPointer<ConfirmBox>>();
const auto sure = [=] {
const auto finish = [=] {
if (*box) {
(*box)->closeBox();
}
};
peer->session().api().inviteLinks().destroyAllRevoked(peer, finish);
};
*box = Ui::show(
Box<ConfirmBox>(tr::lng_group_invite_delete_all_sure(tr::now), sure),
Ui::LayerOption::KeepOther);
}
not_null<Ui::SettingsButton*> AddCreateLinkButton(
not_null<Ui::VerticalLayout*> container) {
const auto result = container->add(
object_ptr<Ui::SettingsButton>(
container,
tr::lng_group_invite_add(),
st::inviteLinkCreate),
style::margins(0, st::inviteLinkCreateSkip, 0, 0));
const auto icon = Ui::CreateChild<Ui::RpWidget>(result);
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto size = st::inviteLinkCreateIconSize;
icon->resize(size, size);
result->heightValue(
) | rpl::start_with_next([=](int height) {
const auto &st = st::inviteLinkList.item;
icon->move(
st.photoPosition.x() + (st.photoSize - size) / 2,
(height - size) / 2);
}, icon->lifetime());
icon->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(icon);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgActive);
const auto rect = icon->rect();
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(rect);
st::inviteLinkCreateIcon.paintInCenter(p, rect);
}, icon->lifetime());
return result;
}
Row::Row(
not_null<RowDelegate*> delegate,
const InviteLinkData &data,
TimeId now)
: PeerListRow(ComputeRowId(data))
, _delegate(delegate)
, _data(data)
, _progressTillExpire(ComputeProgress(data, now))
, _color(ComputeColor(data, _progressTillExpire)) {
setCustomStatus(ComputeStatus(data, now));
}
void Row::update(const InviteLinkData &data, TimeId now) {
_data = data;
_progressTillExpire = ComputeProgress(data, now);
_color = ComputeColor(data, _progressTillExpire);
setCustomStatus(ComputeStatus(data, now));
_delegate->rowUpdateRow(this);
}
void Row::updateExpireProgress(TimeId now) {
const auto updated = ComputeProgress(_data, now);
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
_progressTillExpire = updated;
const auto color = ComputeColor(_data, _progressTillExpire);
if (_color != color) {
_color = color;
setCustomStatus(ComputeStatus(_data, now));
}
_delegate->rowUpdateRow(this);
}
}
InviteLinkData Row::data() const {
return _data;
}
crl::time Row::updateExpireIn() const {
if (_color != Color::Expiring && _color != Color::ExpireSoon) {
return 0;
}
const auto start = _data.startDate ? _data.startDate : _data.date;
if (_data.expireDate <= start) {
return 0;
}
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
}
QString Row::generateName() {
auto result = _data.link;
return result.replace(qstr("https://"), QString());
}
QString Row::generateShortName() {
return generateName();
}
PaintRoundImageCallback Row::generatePaintUserpicCallback() {
return [=](
Painter &p,
int x,
int y,
int outerWidth,
int size) {
_delegate->rowPaintIcon(p, x, y, size, _progressTillExpire, _color);
};
}
QSize Row::actionSize() const {
return QSize(
st::inviteLinkThreeDotsIcon.width(),
st::inviteLinkThreeDotsIcon.height());
}
QMargins Row::actionMargins() const {
return QMargins(
0,
(st::inviteLinkList.item.height - actionSize().height()) / 2,
st::inviteLinkThreeDotsSkip,
0);
}
void Row::paintAction(
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 RowDelegate
, public base::has_weak_ptr {
public:
Controller(not_null<PeerData*> peer, bool revoked);
void prepare() override;
void loadMoreRows() override;
void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
Main::Session &session() const override;
void rowUpdateRow(not_null<Row*> row) override;
void rowPaintIcon(
QPainter &p,
int x,
int y,
int size,
float64 progress,
Color color) override;
private:
void appendRow(const InviteLinkData &data, TimeId now);
void prependRow(const InviteLinkData &data, TimeId now);
void updateRow(const InviteLinkData &data, TimeId now);
bool removeRow(const QString &link);
void appendSlice(const InviteLinksSlice &slice);
void checkExpiringTimer(not_null<Row*> row);
void expiringProgressTimer();
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row);
const not_null<PeerData*> _peer;
const bool _revoked = false;
base::unique_qptr<Ui::PopupMenu> _menu;
QString _offsetLink;
TimeId _offsetDate = 0;
bool _requesting = false;
bool _allLoaded = false;
base::flat_set<not_null<Row*>> _expiringRows;
base::Timer _updateExpiringTimer;
std::array<QImage, int(Color::Count)> _icons;
rpl::lifetime _lifetime;
};
Controller::Controller(not_null<PeerData*> peer, bool revoked)
: _peer(peer)
, _revoked(revoked)
, _updateExpiringTimer([=] { expiringProgressTimer(); }) {
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &image : _icons) {
image = QImage();
}
}, _lifetime);
peer->session().api().inviteLinks().updates(
peer
) | rpl::start_with_next([=](const Api::InviteLinkUpdate &update) {
const auto now = base::unixtime::now();
if (!update.now || update.now->revoked != _revoked) {
if (removeRow(update.was)) {
delegate()->peerListRefreshRows();
}
} else if (update.was.isEmpty()) {
prependRow(*update.now, now);
delegate()->peerListRefreshRows();
} else {
updateRow(*update.now, now);
}
}, _lifetime);
if (_revoked) {
peer->session().api().inviteLinks().allRevokedDestroyed(
peer
) | rpl::start_with_next([=] {
_requesting = false;
_allLoaded = true;
while (delegate()->peerListFullRowsCount()) {
delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
}
delegate()->peerListRefreshRows();
}, _lifetime);
}
}
void Controller::prepare() {
if (!_revoked) {
appendSlice(_peer->session().api().inviteLinks().links(_peer));
}
if (!delegate()->peerListFullRowsCount()) {
loadMoreRows();
}
}
void Controller::loadMoreRows() {
if (_requesting || _allLoaded) {
return;
}
_requesting = true;
const auto done = [=](const InviteLinksSlice &slice) {
if (!_requesting) {
return;
}
_requesting = false;
if (slice.links.empty()) {
_allLoaded = true;
return;
}
appendSlice(slice);
};
_peer->session().api().inviteLinks().requestMoreLinks(
_peer,
_offsetDate,
_offsetLink,
_revoked,
crl::guard(this, done));
}
void Controller::appendSlice(const InviteLinksSlice &slice) {
const auto now = base::unixtime::now();
for (const auto &link : slice.links) {
if (!link.permanent || link.revoked) {
appendRow(link, now);
}
_offsetLink = link.link;
_offsetDate = link.date;
}
if (slice.links.size() >= slice.count) {
_allLoaded = true;
}
delegate()->peerListRefreshRows();
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data());
}
void Controller::rowActionClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, nullptr);
}
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 real = static_cast<Row*>(row.get());
const auto data = real->data();
const auto link = data.link;
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
if (data.revoked) {
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
DeleteLink(_peer, link);
});
} else {
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
CopyInviteLink(link);
});
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
ShareInviteLinkBox(_peer, link);
});
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
EditLink(_peer, data);
});
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] {
RevokeLink(_peer, link);
});
}
return result;
}
Main::Session &Controller::session() const {
return _peer->session();
}
void Controller::appendRow(const InviteLinkData &data, TimeId now) {
delegate()->peerListAppendRow(std::make_unique<Row>(this, data, now));
}
void Controller::prependRow(const InviteLinkData &data, TimeId now) {
delegate()->peerListPrependRow(std::make_unique<Row>(this, data, now));
}
void Controller::updateRow(const InviteLinkData &data, TimeId now) {
if (const auto row = delegate()->peerListFindRow(ComputeRowId(data))) {
const auto real = static_cast<Row*>(row);
real->update(data, now);
checkExpiringTimer(real);
delegate()->peerListUpdateRow(row);
}
}
bool Controller::removeRow(const QString &link) {
if (const auto row = delegate()->peerListFindRow(ComputeRowId(link))) {
delegate()->peerListRemoveRow(row);
return true;
}
return false;
}
void Controller::checkExpiringTimer(not_null<Row*> row) {
const auto updateIn = row->updateExpireIn();
if (updateIn > 0) {
_expiringRows.emplace(row);
if (!_updateExpiringTimer.isActive()
|| updateIn < _updateExpiringTimer.remainingTime()) {
_updateExpiringTimer.callOnce(updateIn);
}
} else {
_expiringRows.remove(row);
}
}
void Controller::expiringProgressTimer() {
const auto now = base::unixtime::now();
auto minimalIn = 0;
for (auto i = begin(_expiringRows); i != end(_expiringRows);) {
(*i)->updateExpireProgress(now);
const auto updateIn = (*i)->updateExpireIn();
if (!updateIn) {
i = _expiringRows.erase(i);
} else {
++i;
if (!minimalIn || minimalIn > updateIn) {
minimalIn = updateIn;
}
}
}
if (minimalIn) {
_updateExpiringTimer.callOnce(minimalIn);
}
}
void Controller::rowUpdateRow(not_null<Row*> row) {
delegate()->peerListUpdateRow(row);
}
void Controller::rowPaintIcon(
QPainter &p,
int x,
int y,
int size,
float64 progress,
Color color) {
const auto skip = st::inviteLinkIconSkip;
const auto inner = size - 2 * skip;
const auto bg = [&] {
switch (color) {
case Color::Permanent: return &st::msgFile1Bg;
case Color::Expiring: return &st::msgFile2Bg;
case Color::ExpireSoon: return &st::msgFile4Bg;
case Color::Expired: return &st::msgFile3Bg;
case Color::Revoked: return &st::windowSubTextFg;
}
Unexpected("Color in Controller::rowPaintIcon.");
}();
auto &icon = _icons[int(color)];
if (icon.isNull()) {
icon = QImage(
QSize(inner, inner) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
icon.fill(Qt::transparent);
icon.setDevicePixelRatio(style::DevicePixelRatio());
auto p = QPainter(&icon);
p.setPen(Qt::NoPen);
p.setBrush(*bg);
auto hq = PainterHighQualityEnabler(p);
p.drawEllipse(0, 0, inner, inner);
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
}
p.drawImage(x + skip, y + skip, icon);
if (progress >= 0. && progress < 1.) {
const auto stroke = st::inviteLinkIconStroke;
auto hq = PainterHighQualityEnabler(p);
auto pen = QPen((*bg)->c);
pen.setWidth(stroke);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
const auto margins = 1.5 * stroke;
p.drawArc(QRectF(x + skip, y + skip, inner, inner).marginsAdded({
margins,
margins,
margins,
margins,
}), (kFullArcLength / 4), kFullArcLength * (1. - progress));
}
}
} // namespace
not_null<Ui::RpWidget*> AddLinksList(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
bool revoked) {
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<Controller>(
peer,
revoked);
controller->setStyleOverrides(&st::inviteLinkList);
const auto content = container->add(object_ptr<PeerListContent>(
container,
controller));
delegate->setContent(content);
controller->setDelegate(delegate);
return content;
}
void ManageInviteLinksBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer) {
using namespace Settings;
box->setTitle(tr::lng_group_invite_title());
const auto container = box->verticalLayout();
AddSubsectionTitle(container, tr::lng_create_permanent_link_title());
AddPermanentLinkBlock(container, peer);
AddDivider(container);
const auto add = AddCreateLinkButton(container);
add->setClickedCallback([=] {
EditLink(peer, InviteLinkData{ .admin = peer->session().user() });
});
const auto list = AddLinksList(container, peer, false);
const auto dividerAbout = container->add(object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_group_invite_add_about(),
st::boxDividerLabel),
st::settingsDividerLabelPadding)),
style::margins(0, st::inviteLinkCreateSkip, 0, 0));
const auto divider = container->add(object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::BoxContentDivider>(container)));
const auto header = container->add(object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_group_invite_revoked_title(),
st::settingsSubsectionTitle),
st::inviteLinkRevokedTitlePadding));
const auto revoked = AddLinksList(container, peer, true);
const auto deleteAll = Ui::CreateChild<Ui::LinkButton>(
container.get(),
tr::lng_group_invite_context_delete_all(tr::now),
st::defaultLinkButton);
rpl::combine(
header->topValue(),
container->widthValue()
) | rpl::start_with_next([=](int top, int outerWidth) {
deleteAll->moveToRight(
st::inviteLinkRevokedTitlePadding.left(),
top + st::inviteLinkRevokedTitlePadding.top(),
outerWidth);
}, deleteAll->lifetime());
deleteAll->setClickedCallback([=] {
DeleteAllRevoked(peer);
});
rpl::combine(
list->heightValue(),
revoked->heightValue()
) | rpl::start_with_next([=](int list, int revoked) {
dividerAbout->toggle(!list, anim::type::instant);
divider->toggle(list > 0 && revoked > 0, anim::type::instant);
header->toggle(revoked > 0, anim::type::instant);
deleteAll->setVisible(revoked > 0);
}, header->lifetime());
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
}

View File

@@ -7,8 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#pragma warning(push)
// class has virtual functions, but destructor is not virtual
#pragma warning(disable:4265)
#include <wrl/implements.h>
#pragma warning(pop)
#include "ui/layers/generic_box.h"
class PeerData;
void ManageInviteLinksBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer);

View File

@@ -8,11 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_type_box.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "main/main_session.h"
#include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.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"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/application.h"
#include "data/data_channel.h"
@@ -31,15 +35,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/special_fields.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_settings.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
@@ -57,7 +64,7 @@ public:
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<Privacy> privacySavedValue,
Privacy privacySavedValue,
std::optional<QString> usernameSavedValue);
void createContent();
@@ -65,17 +72,11 @@ public:
void setFocusUsername();
rpl::producer<QString> getTitle() {
return _isInviteLink
? tr::lng_profile_invite_link_section()
: _isGroup
return _isGroup
? tr::lng_manage_peer_group_type()
: tr::lng_manage_peer_channel_type();
}
bool isInviteLink() {
return _isInviteLink;
}
bool isAllowSave() {
return _isAllowSave;
}
@@ -97,19 +98,14 @@ private:
base::unique_qptr<Ui::FlatLabel> usernameResult;
const style::FlatLabel *usernameResultStyle = nullptr;
Ui::SlideWrap<Ui::RpWidget> *createInviteLinkWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *editInviteLinkWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *inviteLinkWrap = nullptr;
Ui::FlatLabel *inviteLink = nullptr;
};
Controls _controls;
object_ptr<Ui::RpWidget> createPrivaciesEdit();
object_ptr<Ui::RpWidget> createUsernameEdit();
object_ptr<Ui::RpWidget> createInviteLinkCreate();
object_ptr<Ui::RpWidget> createInviteLinkEdit();
void observeInviteLink();
object_ptr<Ui::RpWidget> createInviteLinkBlock();
void privacyChanged(Privacy value);
@@ -122,13 +118,6 @@ private:
rpl::producer<QString> &&text,
not_null<const style::FlatLabel*> st);
bool canEditInviteLink() const;
void refreshEditInviteLink();
void refreshCreateInviteLink();
void createInviteLink();
void revokeInviteLink();
void exportInviteLink(const QString &confirmation);
void fillPrivaciesButtons(
not_null<Ui::VerticalLayout*> parent,
std::optional<Privacy> savedValue = std::nullopt);
@@ -143,12 +132,11 @@ private:
not_null<PeerData*> _peer;
MTP::Sender _api;
std::optional<Privacy> _privacySavedValue;
Privacy _privacySavedValue = Privacy();
std::optional<QString> _usernameSavedValue;
bool _useLocationPhrases = false;
bool _isGroup = false;
bool _isInviteLink = false;
bool _isAllowSave = false;
base::unique_qptr<Ui::VerticalLayout> _wrap;
@@ -165,7 +153,7 @@ Controller::Controller(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<Privacy> privacySavedValue,
Privacy privacySavedValue,
std::optional<QString> usernameSavedValue)
: _peer(peer)
, _api(&_peer->session().mtp())
@@ -173,8 +161,6 @@ Controller::Controller(
, _usernameSavedValue(usernameSavedValue)
, _useLocationPhrases(useLocationPhrases)
, _isGroup(_peer->isChat() || _peer->isMegagroup())
, _isInviteLink(!_privacySavedValue.has_value()
&& !_usernameSavedValue.has_value())
, _isAllowSave(!_usernameSavedValue.value_or(QString()).isEmpty())
, _wrap(container)
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
@@ -184,20 +170,28 @@ Controller::Controller(
void Controller::createContent() {
_controls = Controls();
if (_isInviteLink) {
_wrap->add(createInviteLinkCreate());
_wrap->add(createInviteLinkEdit());
return;
}
fillPrivaciesButtons(_wrap, _privacySavedValue);
// Skip.
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
//
_wrap->add(createInviteLinkCreate());
_wrap->add(createInviteLinkEdit());
_wrap->add(createInviteLinkBlock());
_wrap->add(createUsernameEdit());
//using namespace Settings; // #TODO links
//AddSkip(_wrap.get());
//_wrap->add(EditPeerInfoBox::CreateButton(
// _wrap.get(),
// tr::lng_group_invite_manage(),
// rpl::single(QString()),
// [=] { Ui::show(
// Box(ManageInviteLinksBox, _peer),
// Ui::LayerOption::KeepOther);
// },
// st::manageGroupButton,
// &st::infoIconInviteLinks));
//AddSkip(_wrap.get());
//AddDividerText(_wrap.get(), tr::lng_group_invite_manage_about());
if (_controls.privacy->value() == Privacy::NoUsername) {
checkUsernameAvailability();
}
@@ -312,21 +306,23 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerUsernameMargins);
object_ptr<Ui::VerticalLayout>(_wrap));
_controls.usernameWrap = result.data();
const auto container = result->entity();
container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
container,
using namespace Settings;
AddSkip(container);
container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_create_group_link(),
st::editPeerSectionLabel),
st::editPeerUsernameTitleLabelMargins));
st::settingsSubsectionTitle),
st::settingsSubsectionTitlePadding);
const auto placeholder = container->add(object_ptr<Ui::RpWidget>(
container));
const auto placeholder = container->add(
object_ptr<Ui::RpWidget>(container),
st::editPeerUsernameFieldMargins);
placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
_controls.usernameInput = Ui::AttachParentChild(
container,
@@ -348,13 +344,9 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
}, placeholder->lifetime());
_controls.usernameInput->move(placeholder->pos());
container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
AddDividerText(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_create_channel_link_about(),
st::editPeerPrivacyLabel),
st::editPeerUsernameAboutLabelMargins));
tr::lng_create_channel_link_about());
QObject::connect(
_controls.usernameInput,
@@ -368,6 +360,11 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
}
void Controller::privacyChanged(Privacy value) {
const auto toggleInviteLink = [&] {
_controls.inviteLinkWrap->toggle(
(value != Privacy::HasUsername),
anim::type::instant);
};
const auto toggleEditUsername = [&] {
_controls.usernameWrap->toggle(
(value == Privacy::HasUsername),
@@ -378,16 +375,14 @@ void Controller::privacyChanged(Privacy value) {
// Otherwise box will change own Y position.
if (value == Privacy::HasUsername) {
refreshCreateInviteLink();
refreshEditInviteLink();
toggleInviteLink();
toggleEditUsername();
_controls.usernameResult = nullptr;
checkUsernameAvailability();
} else {
toggleEditUsername();
refreshCreateInviteLink();
refreshEditInviteLink();
toggleInviteLink();
}
};
if (value == Privacy::HasUsername) {
@@ -535,187 +530,46 @@ void Controller::showUsernameResult(
_usernameResultTexts.fire(std::move(text));
}
void Controller::createInviteLink() {
exportInviteLink((_isGroup
? tr::lng_group_invite_about
: tr::lng_group_invite_about_channel)(tr::now));
}
void Controller::revokeInviteLink() {
exportInviteLink(tr::lng_group_invite_about_new(tr::now));
}
void Controller::exportInviteLink(const QString &confirmation) {
const auto callback = crl::guard(this, [=](Fn<void()> &&close) {
close();
_peer->session().api().exportInviteLink(_peer->migrateToOrMe());
});
auto box = Box<ConfirmBox>(
confirmation,
std::move(callback));
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
}
bool Controller::canEditInviteLink() const {
if (const auto channel = _peer->asChannel()) {
return channel->amCreator()
|| (channel->adminRights() & ChatAdminRight::f_invite_users);
} else if (const auto chat = _peer->asChat()) {
return chat->amCreator()
|| (chat->adminRights() & ChatAdminRight::f_invite_users);
}
return false;
}
void Controller::observeInviteLink() {
if (!_controls.editInviteLinkWrap) {
return;
}
_peer->session().changes().peerFlagsValue(
_peer,
Data::PeerUpdate::Flag::InviteLink
) | rpl::start_with_next([=] {
refreshCreateInviteLink();
refreshEditInviteLink();
}, _controls.editInviteLinkWrap->lifetime());
}
object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
Expects(_wrap != nullptr);
if (!canEditInviteLink()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
_controls.editInviteLinkWrap = result.data();
object_ptr<Ui::VerticalLayout>(_wrap));
_controls.inviteLinkWrap = result.data();
const auto container = result->entity();
if (!_isInviteLink) {
container->add(object_ptr<Ui::FlatLabel>(
container,
tr::lng_profile_invite_link_section(),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkBoxBottomSkip));
}
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
container,
st::editPeerInviteLink));
_controls.inviteLink->setSelectable(true);
_controls.inviteLink->setContextCopyText(QString());
_controls.inviteLink->setBreakEverywhere(true);
_controls.inviteLink->setClickHandlerFilter([=](auto&&...) {
QGuiApplication::clipboard()->setText(inviteLinkText());
Ui::Toast::Show(tr::lng_group_invite_copied(tr::now));
return false;
});
using namespace Settings;
AddSkip(container);
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
container->add(object_ptr<Ui::LinkButton>(
container,
tr::lng_group_invite_create_new(tr::now),
st::editPeerInviteLinkButton)
)->addClickHandler([=] { revokeInviteLink(); });
AddSubsectionTitle(container, tr::lng_create_permanent_link_title());
AddPermanentLinkBlock(container, _peer);
observeInviteLink();
AddSkip(container);
AddDividerText(
container,
((_peer->isMegagroup() || _peer->asChat())
? tr::lng_group_invite_about_permanent_group()
: tr::lng_group_invite_about_permanent_channel()));
return result;
}
void Controller::refreshEditInviteLink() {
const auto link = inviteLinkText();
auto text = TextWithEntities();
if (!link.isEmpty()) {
text.text = link;
const auto remove = qstr("https://");
if (text.text.startsWith(remove)) {
text.text.remove(0, remove.size());
}
text.entities.push_back({
EntityType::CustomUrl,
0,
text.text.size(),
link });
}
_controls.inviteLink->setMarkedText(text);
// Hack to expand FlatLabel width to naturalWidth again.
_controls.editInviteLinkWrap->resizeToWidth(st::boxWideWidth);
_controls.editInviteLinkWrap->toggle(
inviteLinkShown() && !link.isEmpty(),
anim::type::instant);
}
object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() {
Expects(_wrap != nullptr);
if (!canEditInviteLink()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
const auto container = result->entity();
if (!_isInviteLink) {
container->add(object_ptr<Ui::FlatLabel>(
container,
tr::lng_profile_invite_link_section(),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
}
container->add(object_ptr<Ui::LinkButton>(
_wrap,
tr::lng_group_invite_create(tr::now),
st::editPeerInviteLinkButton)
)->addClickHandler([this] {
createInviteLink();
});
_controls.createInviteLinkWrap = result.data();
observeInviteLink();
return result;
}
void Controller::refreshCreateInviteLink() {
_controls.createInviteLinkWrap->toggle(
inviteLinkShown() && inviteLinkText().isEmpty(),
anim::type::instant);
}
bool Controller::inviteLinkShown() {
return !_controls.privacy
|| (_controls.privacy->value() == Privacy::NoUsername)
|| _isInviteLink;
|| (_controls.privacy->value() == Privacy::NoUsername);
}
} // namespace
EditPeerTypeBox::EditPeerTypeBox(QWidget*, not_null<PeerData*> peer)
: EditPeerTypeBox(nullptr, peer, false, {}, {}, {}, {}) {
}
EditPeerTypeBox::EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
std::optional<Privacy> privacySaved,
Privacy privacySaved,
std::optional<QString> usernameSaved,
std::optional<rpl::producer<QString>> usernameError)
: _peer(peer)
@@ -733,11 +587,11 @@ void EditPeerTypeBox::setInnerFocus() {
void EditPeerTypeBox::prepare() {
_peer->updateFull();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
auto content = object_ptr<Ui::VerticalLayout>(this);
const auto controller = Ui::CreateChild<Controller>(
this,
content,
content.data(),
_peer,
_useLocationPhrases,
_privacySavedValue,
@@ -756,7 +610,7 @@ void EditPeerTypeBox::prepare() {
setTitle(controller->getTitle());
if (!controller->isInviteLink() && _savedCallback.has_value()) {
if (_savedCallback.has_value()) {
addButton(tr::lng_settings_save(), [=] {
const auto v = controller->getPrivacy();
if (!controller->isAllowSave() && (v == Privacy::HasUsername)) {
@@ -768,13 +622,12 @@ void EditPeerTypeBox::prepare() {
local(v,
(v == Privacy::HasUsername)
? controller->getUsernameInput()
: QString()); // We dont need username with private type.
: QString()); // We don't need username with private type.
closeBox();
});
}
addButton(
controller->isInviteLink() ? tr::lng_close() : tr::lng_cancel(),
[=] { closeBox(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
setDimensionsToContent(st::boxWideWidth, content);
setDimensionsToContent(st::boxWideWidth, content.data());
setInnerWidget(std::move(content));
}

View File

@@ -32,15 +32,12 @@ enum class UsernameState {
class EditPeerTypeBox : public Ui::BoxContent {
public:
// Edit just the invite link.
EditPeerTypeBox(QWidget*, not_null<PeerData*> peer);
EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer,
bool useLocationPhrases,
std::optional<FnMut<void(Privacy, QString)>> savedCallback,
std::optional<Privacy> privacySaved,
Privacy privacySaved,
std::optional<QString> usernameSaved,
std::optional<rpl::producer<QString>> usernameError = {});
@@ -53,7 +50,7 @@ private:
bool _useLocationPhrases = false;
std::optional<FnMut<void(Privacy, QString)>> _savedCallback;
std::optional<Privacy> _privacySavedValue;
Privacy _privacySavedValue = Privacy();
std::optional<QString> _usernameSavedValue;
std::optional<rpl::producer<QString>> _usernameError;

View File

@@ -73,10 +73,9 @@ void ReportBox::prepare() {
st::defaultBoxCheckbox);
};
createButton(_reasonSpam, Reason::Spam, tr::lng_report_reason_spam(tr::now));
createButton(_reasonFake, Reason::Fake, tr::lng_report_reason_fake(tr::now));
createButton(_reasonViolence, Reason::Violence, tr::lng_report_reason_violence(tr::now));
if (_ids) {
createButton(_reasonChildAbuse, Reason::ChildAbuse, tr::lng_report_reason_child_abuse(tr::now));
}
createButton(_reasonChildAbuse, Reason::ChildAbuse, tr::lng_report_reason_child_abuse(tr::now));
createButton(_reasonPornography, Reason::Pornography, tr::lng_report_reason_pornography(tr::now));
createButton(_reasonOther, Reason::Other, tr::lng_report_reason_other(tr::now));
_reasonGroup->setChangedCallback([=](Reason value) {
@@ -90,14 +89,10 @@ void ReportBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_reasonSpam->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _reasonSpam->getMargins().top());
_reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->bottomNoMargins() + st::boxOptionListSkip);
if (_ids) {
_reasonChildAbuse->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip);
_reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonChildAbuse->bottomNoMargins() + st::boxOptionListSkip);
}
else{
_reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip);
}
_reasonFake->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonSpam->bottomNoMargins() + st::boxOptionListSkip);
_reasonViolence->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonFake->bottomNoMargins() + st::boxOptionListSkip);
_reasonChildAbuse->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonViolence->bottomNoMargins() + st::boxOptionListSkip);
_reasonPornography->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonChildAbuse->bottomNoMargins() + st::boxOptionListSkip);
_reasonOther->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _reasonPornography->bottomNoMargins() + st::boxOptionListSkip);
if (_reasonOtherText) {
@@ -156,6 +151,7 @@ void ReportBox::report() {
const auto reason = [&] {
switch (_reasonGroup->value()) {
case Reason::Spam: return MTP_inputReportReasonSpam();
case Reason::Fake: return MTP_inputReportReasonFake();
case Reason::Violence: return MTP_inputReportReasonViolence();
case Reason::ChildAbuse: return MTP_inputReportReasonChildAbuse();
case Reason::Pornography: return MTP_inputReportReasonPornography();
@@ -203,7 +199,7 @@ void ReportBox::reportFail(const RPCError &error) {
}
void ReportBox::updateMaxHeight() {
const auto buttonsCount = _ids ? 5 : 4;
const auto buttonsCount = 6;
auto newHeight = st::boxOptionListPadding.top() + _reasonSpam->getMargins().top() + buttonsCount * _reasonSpam->heightNoMargins() + (buttonsCount - 1) * st::boxOptionListSkip + _reasonSpam->getMargins().bottom() + st::boxOptionListPadding.bottom();
if (_reasonOtherText) {

View File

@@ -37,6 +37,7 @@ protected:
private:
enum class Reason {
Spam,
Fake,
Violence,
ChildAbuse,
Pornography,
@@ -56,6 +57,7 @@ private:
std::shared_ptr<Ui::RadioenumGroup<Reason>> _reasonGroup;
object_ptr<Ui::Radioenum<Reason>> _reasonSpam = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonFake = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonViolence = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonChildAbuse = { nullptr };
object_ptr<Ui::Radioenum<Reason>> _reasonPornography = { nullptr };

View File

@@ -778,6 +778,47 @@ groupCallMajorBlobMaxRadius: 4px;
groupCallMinorBlobIdleRadius: 3px;
groupCallMinorBlobMaxRadius: 12px;
groupCallMuteCrossLine: CrossLineAnimation {
fg: groupCallIconFg;
icon: icon {{ "calls/volume/speaker", groupCallIconFg }};
startPosition: point(2px, 5px);
endPosition: point(16px, 19px);
stroke: 2px;
}
groupCallMenuSpeakerArcsSkip: 1px;
groupCallMenuVolumeSkip: 5px;
groupCallMenuVolumeSlider: MediaSlider(defaultContinuousSlider) {
activeFg: groupCallMembersFg;
inactiveFg: groupCallMemberInactiveIcon;
activeFgOver: groupCallMembersFg;
inactiveFgOver: groupCallMemberInactiveIcon;
activeFgDisabled: groupCallMemberInactiveIcon;
receivedTillFg: groupCallMemberInactiveIcon;
}
groupCallSpeakerArcsAnimation: ArcsAnimation {
fg: groupCallIconFg;
stroke: 2px;
space: 4px;
duration: 200;
deltaAngle: 60;
deltaHeight: 6px;
deltaWidth: 7px;
startHeight: 3px;
startWidth: 0px;
}
groupCallStatusSpeakerIcon: icon {{ "calls/volume/speaker_small", groupCallIconFg }};
groupCallStatusSpeakerArcsSkip: 3px;
groupCallStatusSpeakerArcsAnimation: ArcsAnimation(groupCallSpeakerArcsAnimation) {
deltaAngle: 68;
space: 3px;
deltaHeight: 5px;
deltaWidth: 4px;
startHeight: 1px;
}
callTopBarMuteCrossLine: CrossLineAnimation {
fg: callBarFg;
icon: icon {{ "calls/call_record_active", callBarFg }};

View File

@@ -7,10 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_box_controller.h"
#include "styles/style_calls.h"
#include "styles/style_boxes.h"
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
#include "core/application.h"
#include "calls/calls_instance.h"
#include "history/history.h"
@@ -22,7 +23,14 @@ 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 "base/unixtime.h"
#include "api/api_updates.h"
#include "app.h"
#include "apiwrap.h"
#include "styles/style_layers.h" // st::boxLabel.
#include "styles/style_calls.h"
#include "styles/style_boxes.h"
namespace Calls {
namespace {
@@ -49,6 +57,7 @@ public:
bool canAddItem(not_null<const HistoryItem*> item) const {
return (ComputeType(item) == _type)
&& (!hasItems() || _items.front()->history() == item->history())
&& (ItemDateTime(item).date() == _date);
}
void addItem(not_null<HistoryItem*> item) {
@@ -66,20 +75,26 @@ public:
refreshStatus();
}
}
bool hasItems() const {
[[nodiscard]] bool hasItems() const {
return !_items.empty();
}
MsgId minItemId() const {
[[nodiscard]] MsgId minItemId() const {
Expects(hasItems());
return _items.back()->id;
}
MsgId maxItemId() const {
[[nodiscard]] MsgId maxItemId() const {
Expects(hasItems());
return _items.front()->id;
}
[[nodiscard]] const std::vector<not_null<HistoryItem*>> &items() const {
return _items;
}
void paintStatusText(
Painter &p,
const style::PeerListItem &st,
@@ -333,6 +348,22 @@ void BoxController::loadMoreRows() {
}).send();
}
base::unique_qptr<Ui::PopupMenu> BoxController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto &items = static_cast<Row*>(row.get())->items();
const auto session = &this->session();
const auto ids = session->data().itemsToIds(items);
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
result->addAction(tr::lng_context_delete_selected(tr::now), [=] {
Ui::show(
Box<DeleteMessagesBox>(session, base::duplicate(ids)),
Ui::LayerOption::KeepOther);
});
return result;
}
void BoxController::refreshAbout() {
setDescriptionText(delegate()->peerListFullRowsCount() ? QString() : tr::lng_call_box_about(tr::now));
}
@@ -448,4 +479,64 @@ std::unique_ptr<PeerListRow> BoxController::createRow(
return std::make_unique<Row>(item);
}
void ClearCallsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window) {
const auto weak = Ui::MakeWeak(box);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_call_box_clear_sure(),
st::boxLabel),
st::boxPadding);
const auto revokeCheckbox = box->addRow(
object_ptr<Ui::Checkbox>(
box,
tr::lng_delete_for_everyone_check(tr::now),
false,
st::defaultBoxCheckbox),
style::margins(
st::boxPadding.left(),
st::boxPadding.bottom(),
st::boxPadding.right(),
st::boxPadding.bottom()));
const auto api = &window->session().api();
const auto sendRequest = [=](bool revoke, auto self) -> void {
using Flag = MTPmessages_DeletePhoneCallHistory::Flag;
api->request(MTPmessages_DeletePhoneCallHistory(
MTP_flags(revoke ? Flag::f_revoke : Flag(0))
)).done([=](const MTPmessages_AffectedFoundMessages &result) {
result.match([&](
const MTPDmessages_affectedFoundMessages &data) {
api->applyUpdates(MTP_updates(
MTP_vector<MTPUpdate>(
1,
MTP_updateDeleteMessages(
data.vmessages(),
data.vpts(),
data.vpts_count())),
MTP_vector<MTPUser>(),
MTP_vector<MTPChat>(),
MTP_int(base::unixtime::now()),
MTP_int(0)));
const auto offset = data.voffset().v;
if (offset > 0) {
self(revoke, self);
} else {
api->session().data().destroyAllCallItems();
if (const auto strong = weak.data()) {
strong->closeBox();
}
}
});
}).send();
};
box->addButton(tr::lng_call_box_clear_button(), [=] {
sendRequest(revokeCheckbox->checked(), sendRequest);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
} // namespace Calls

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "boxes/peer_list_box.h"
#include "ui/layers/generic_box.h"
namespace Window {
class SessionController;
@@ -25,6 +26,10 @@ public:
void rowActionClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
private:
void receivedCalls(const QVector<MTPMessage> &result);
void refreshAbout();
@@ -49,4 +54,8 @@ private:
};
void ClearCallsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window);
} // namespace Calls

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_panel.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "facades.h"
@@ -231,7 +232,7 @@ void Call::startOutgoing() {
_api.request(MTPphone_RequestCall(
MTP_flags(flags),
_user->inputUser,
MTP_int(rand_value<int32>()),
MTP_int(openssl::RandomValue<int32>()),
MTP_bytes(_gaHash),
MTP_phoneCallProtocol(
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
@@ -779,6 +780,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
sendSignalingData(bytes);
});
},
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_group_call.h"
#include "calls/calls_group_common.h"
#include "main/main_session.h"
#include "api/api_send_progress.h"
#include "apiwrap.h"
@@ -24,7 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h"
#include "data/data_session.h"
#include "base/global_shortcuts.h"
#include "base/openssl_help.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h"
#include <tgcalls/group/GroupInstanceImpl.h>
@@ -53,6 +56,22 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
settings.callVideoInputDeviceId());
}
[[nodiscard]] const Data::GroupCall::Participant *LookupParticipant(
not_null<PeerData*> chat,
uint64 id,
not_null<UserData*> user) {
const auto call = chat->groupCall();
if (!id || !call || call->id() != id) {
return nullptr;
}
const auto &participants = call->participants();
const auto i = ranges::find(
participants,
user,
&Data::GroupCall::Participant::user);
return (i != end(participants)) ? &*i : nullptr;
}
} // namespace
GroupCall::GroupCall(
@@ -200,7 +219,7 @@ void GroupCall::playConnectingSoundOnce() {
void GroupCall::start() {
_createRequestId = _api.request(MTPphone_CreateGroupCall(
_peer->input,
MTP_int(rand_value<int32>())
MTP_int(openssl::RandomValue<int32>())
)).done([=](const MTPUpdates &result) {
_acceptFields = true;
_peer->session().api().applyUpdates(result);
@@ -236,11 +255,25 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
using Update = Data::GroupCall::ParticipantUpdate;
_peer->groupCall()->participantUpdated(
) | rpl::filter([=](const Update &update) {
return (_instance != nullptr) && !update.now;
return (_instance != nullptr);
}) | rpl::start_with_next([=](const Update &update) {
Expects(update.was.has_value());
_instance->removeSsrcs({ update.was->ssrc });
if (!update.now) {
_instance->removeSsrcs({ update.was->ssrc });
} else {
const auto &now = *update.now;
const auto &was = update.was;
const auto volumeChanged = was
? (was->volume != now.volume || was->mutedByMe != now.mutedByMe)
: (now.volume != Group::kDefaultVolume || now.mutedByMe);
if (volumeChanged) {
_instance->setVolume(
now.ssrc,
(now.mutedByMe
? 0.
: (now.volume
/ float64(Group::kDefaultVolume))));
}
}
}, _lifetime);
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
@@ -359,7 +392,42 @@ void GroupCall::applySelfInCallLocally() {
MTP_int(self->bareId()),
MTP_int(date),
MTP_int(lastActive),
MTP_int(_mySsrc))),
MTP_int(_mySsrc),
MTP_int(Group::kDefaultVolume))), // volume
MTP_int(0)).c_updateGroupCallParticipants());
}
void GroupCall::applyParticipantLocally(
not_null<UserData*> user,
bool mute,
std::optional<int> volume) {
const auto participant = LookupParticipant(_peer, _id, user);
if (!participant || !participant->ssrc) {
return;
}
const auto canSelfUnmute = participant->canSelfUnmute;
const auto mutedCount = 0/*participant->mutedCount*/;
using Flag = MTPDgroupCallParticipant::Flag;
const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0))
| (volume.has_value() ? Flag::f_volume : Flag(0))
| (participant->lastActive ? Flag::f_active_date : Flag(0))
| (!mute
? Flag(0)
: user->canManageGroupCall()
? Flag::f_muted
: Flag::f_muted_by_you);
_peer->groupCall()->applyUpdateChecked(
MTP_updateGroupCallParticipants(
inputCall(),
MTP_vector<MTPGroupCallParticipant>(
1,
MTP_groupCallParticipant(
MTP_flags(flags),
MTP_int(user->bareId()),
MTP_int(participant->date),
MTP_int(participant->lastActive),
MTP_int(participant->ssrc),
MTP_int(volume.value_or(participant->volume)))),
MTP_int(0)).c_updateGroupCallParticipants());
}
@@ -525,10 +593,25 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
return;
}
const auto handleOtherParticipants = [=](
const MTPDgroupCallParticipant &data) {
const auto user = _peer->owner().user(data.vuser_id().v);
const auto participant = LookupParticipant(_peer, _id, user);
if (!participant) {
return;
}
_otherParticipantStateValue.fire(Group::ParticipantState{
.user = user,
.volume = data.vvolume().value_or_empty(),
.mutedByMe = data.is_muted_by_you(),
});
};
const auto self = _peer->session().userId();
for (const auto &participant : data.vparticipants().v) {
participant.match([&](const MTPDgroupCallParticipant &data) {
if (data.vuser_id().v != self) {
handleOtherParticipants(data);
return;
}
if (data.is_left() && data.vsource().v == _mySsrc) {
@@ -581,6 +664,8 @@ void GroupCall::createAndStartController() {
},
.initialInputDeviceId = _audioInputId.toStdString(),
.initialOutputDeviceId = _audioOutputId.toStdString(),
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
@@ -602,6 +687,7 @@ void GroupCall::createAndStartController() {
std::move(descriptor));
updateInstanceMuteState();
updateInstanceVolumes();
//raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
}
@@ -614,6 +700,26 @@ void GroupCall::updateInstanceMuteState() {
&& state != MuteState::PushToTalk);
}
void GroupCall::updateInstanceVolumes() {
const auto real = _peer->groupCall();
if (!real || real->id() != _id) {
return;
}
const auto &participants = real->participants();
for (const auto &participant : participants) {
const auto setVolume = participant.mutedByMe
|| (participant.volume != Group::kDefaultVolume);
if (setVolume && participant.ssrc) {
_instance->setVolume(
participant.ssrc,
(participant.mutedByMe
? 0.
: (participant.volume / float64(Group::kDefaultVolume))));
}
}
}
void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
Expects(!data.updates.empty());
@@ -752,7 +858,8 @@ void GroupCall::sendMutedUpdate() {
? MTPphone_EditGroupCallMember::Flag::f_muted
: MTPphone_EditGroupCallMember::Flag(0)),
inputCall(),
MTP_inputUserSelf()
MTP_inputUserSelf(),
MTP_int(100000) // volume
)).done([=](const MTPUpdates &result) {
_updateMuteRequestId = 0;
_peer->session().api().applyUpdates(result);
@@ -783,16 +890,40 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
}
}
void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
if (!_id) {
void GroupCall::toggleMute(const Group::MuteRequest &data) {
if (data.locallyOnly) {
applyParticipantLocally(data.user, data.mute, std::nullopt);
} else {
editParticipant(data.user, data.mute, std::nullopt);
}
}
void GroupCall::changeVolume(const Group::VolumeRequest &data) {
if (data.locallyOnly) {
applyParticipantLocally(data.user, false, data.volume);
} else {
editParticipant(data.user, false, data.volume);
}
}
void GroupCall::editParticipant(
not_null<UserData*> user,
bool mute,
std::optional<int> volume) {
const auto participant = LookupParticipant(_peer, _id, user);
if (!participant) {
return;
}
applyParticipantLocally(user, mute, volume);
using Flag = MTPphone_EditGroupCallMember::Flag;
const auto flags = (mute ? Flag::f_muted : Flag(0))
| (volume.has_value() ? Flag::f_volume : Flag(0));
_api.request(MTPphone_EditGroupCallMember(
MTP_flags(mute
? MTPphone_EditGroupCallMember::Flag::f_muted
: MTPphone_EditGroupCallMember::Flag(0)),
MTP_flags(flags),
inputCall(),
user->inputUser
user->inputUser,
MTP_int(std::clamp(volume.value_or(0), 1, Group::kMaxVolume))
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
}).fail([=](const RPCError &error) {
@@ -918,6 +1049,11 @@ void GroupCall::pushToTalkCancel() {
}
}
auto GroupCall::otherParticipantStateValue() const
-> rpl::producer<Group::ParticipantState> {
return _otherParticipantStateValue.events();
}
//void GroupCall::setAudioVolume(bool input, float level) {
// if (_instance) {
// if (input) {

View File

@@ -35,6 +35,12 @@ struct LastSpokeTimes;
namespace Calls {
namespace Group {
struct MuteRequest;
struct VolumeRequest;
struct ParticipantState;
} // namespace Group
enum class MuteState {
Active,
PushToTalk,
@@ -104,6 +110,9 @@ public:
return _muted.value();
}
[[nodiscard]] auto otherParticipantStateValue() const
-> rpl::producer<Group::ParticipantState>;
enum State {
Creating,
Joining,
@@ -131,7 +140,8 @@ public:
//void setAudioVolume(bool input, float level);
void setAudioDuckingEnabled(bool enabled);
void toggleMute(not_null<UserData*> user, bool mute);
void toggleMute(const Group::MuteRequest &data);
void changeVolume(const Group::VolumeRequest &data);
std::variant<int, not_null<UserData*>> inviteUsers(
const std::vector<not_null<UserData*>> &users);
@@ -163,6 +173,7 @@ private:
void maybeSendMutedUpdate(MuteState previous);
void sendMutedUpdate();
void updateInstanceMuteState();
void updateInstanceVolumes();
void applySelfInCallLocally();
void rejoin();
@@ -178,6 +189,15 @@ private:
void stopConnectingSound();
void playConnectingSoundOnce();
void editParticipant(
not_null<UserData*> user,
bool mute,
std::optional<int> volume);
void applyParticipantLocally(
not_null<UserData*> user,
bool mute,
std::optional<int> volume);
[[nodiscard]] MTPInputGroupCall inputCall() const;
const not_null<Delegate*> _delegate;
@@ -190,6 +210,8 @@ private:
rpl::variable<MuteState> _muted = MuteState::Muted;
bool _acceptFields = false;
rpl::event_stream<Group::ParticipantState> _otherParticipantStateValue;
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;

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
class UserData;
namespace Calls::Group {
constexpr auto kDefaultVolume = 10000;
constexpr auto kMaxVolume = 20000;
struct MuteRequest {
not_null<UserData*> user;
bool mute = false;
bool locallyOnly = false;
};
struct VolumeRequest {
not_null<UserData*> user;
int volume = kDefaultVolume;
bool finalized = true;
bool locallyOnly = false;
};
struct ParticipantState {
not_null<UserData*> user;
std::optional<int> volume;
bool mutedByMe = false;
bool locallyOnly = false;
};
} // namespace Calls::Group

View File

@@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_members.h"
#include "calls/calls_group_call.h"
#include "calls/calls_group_common.h"
#include "calls/calls_volume_item.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@@ -16,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h" // Data::CanWriteValue.
#include "data/data_session.h" // Data::Session::invitedToCallUsers.
#include "settings/settings_common.h" // Settings::CreateButton.
#include "ui/paint/arcs.h"
#include "ui/paint/blobs.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
@@ -44,6 +47,12 @@ constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kWideScale = 5;
const auto kSpeakerThreshold = std::vector<float>{
Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
Group::kDefaultVolume * 0.9f / Group::kMaxVolume };
constexpr auto kArcsStrokeRatio = 0.8;
auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
return { {
{
@@ -76,7 +85,8 @@ public:
QRect rect,
float64 speaking,
float64 active,
float64 muted) = 0;
float64 muted,
bool mutedByMe) = 0;
};
class Row final : public PeerListRow {
@@ -87,6 +97,7 @@ public:
Active,
Inactive,
Muted,
MutedByMe,
Invited,
};
@@ -106,6 +117,9 @@ public:
[[nodiscard]] bool speaking() const {
return _speaking;
}
[[nodiscard]] int volume() const {
return _volume;
}
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
void stopLastActionRipple() override;
@@ -172,11 +186,37 @@ private:
rpl::lifetime lifetime;
};
struct StatusIcon {
StatusIcon(float volume)
: speaker(st::groupCallStatusSpeakerIcon)
, arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
st::groupCallStatusSpeakerArcsAnimation,
kSpeakerThreshold,
volume,
Ui::Paint::ArcsAnimation::HorizontalDirection::Right)) {
}
const style::icon &speaker;
const std::unique_ptr<Ui::Paint::ArcsAnimation> arcs;
int arcsWidth = 0;
rpl::lifetime lifetime;
};
int statusIconWidth() const;
int statusIconHeight() const;
void paintStatusIcon(
Painter &p,
const style::PeerListItem &st,
const style::font &font,
bool selected);
void refreshStatus() override;
void setSounding(bool sounding);
void setSpeaking(bool speaking);
void setState(State state);
void setSsrc(uint32 ssrc);
void setVolume(int volume);
void ensureUserpicCache(
std::shared_ptr<Data::CloudImageView> &view,
@@ -186,10 +226,13 @@ private:
State _state = State::Inactive;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
std::unique_ptr<BlobsAnimation> _blobsAnimation;
std::unique_ptr<StatusIcon> _statusIcon;
Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon.
Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
Ui::Animations::Simple _activeAnimation; // For icon cross animation.
Ui::Animations::Simple _arcsAnimation; // For volume arcs animation.
uint32 _ssrc = 0;
int _volume = Group::kDefaultVolume;
bool _sounding = false;
bool _speaking = false;
bool _skipLevelUpdate = false;
@@ -206,7 +249,8 @@ public:
not_null<QWidget*> menuParent);
~MembersController();
using MuteRequest = GroupMembers::MuteRequest;
using MuteRequest = Group::MuteRequest;
using VolumeRequest = Group::VolumeRequest;
Main::Session &session() const override;
void prepare() override;
@@ -221,6 +265,7 @@ public:
return _fullCount.value();
}
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
[[nodiscard]] rpl::producer<VolumeRequest> changeVolumeRequests() const;
[[nodiscard]] auto kickMemberRequests() const
-> rpl::producer<not_null<UserData*>>;
@@ -231,7 +276,8 @@ public:
QRect rect,
float64 speaking,
float64 active,
float64 muted) override;
float64 muted,
bool mutedByMe) override;
private:
[[nodiscard]] std::unique_ptr<Row> createSelfRow();
@@ -246,6 +292,10 @@ private:
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row);
void addMuteActionsToContextMenu(
not_null<Ui::PopupMenu*> menu,
not_null<UserData*> user,
not_null<Row*> row);
void setupListChangeViewers(not_null<GroupCall*> call);
void subscribeToChanges(not_null<Data::GroupCall*> real);
void updateRow(
@@ -271,8 +321,11 @@ private:
bool _prepared = false;
rpl::event_stream<MuteRequest> _toggleMuteRequests;
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
rpl::event_stream<not_null<UserData*>> _kickMemberRequests;
rpl::variable<int> _fullCount = 1;
rpl::variable<int> _fullCountMin = 0;
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
not_null<QWidget*> _menuParent;
base::unique_qptr<Ui::PopupMenu> _menu;
@@ -303,17 +356,24 @@ void Row::setSkipLevelUpdate(bool value) {
void Row::updateState(const Data::GroupCall::Participant *participant) {
setSsrc(participant ? participant->ssrc : 0);
setVolume(participant
? participant->volume
: Group::kDefaultVolume);
if (!participant) {
setState(State::Invited);
setSounding(false);
setSpeaking(false);
} else if (!participant->muted
|| (participant->sounding && participant->ssrc != 0)) {
setState(State::Active);
setState(participant->mutedByMe
? State::MutedByMe
: (participant->sounding || participant->speaking)
? State::Active
: State::Inactive);
setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0);
} else if (participant->canSelfUnmute) {
setState(State::Inactive);
setState(participant->mutedByMe ? State::MutedByMe : State::Inactive);
setSounding(false);
setSpeaking(false);
} else {
@@ -333,6 +393,42 @@ void Row::setSpeaking(bool speaking) {
_speaking ? 0. : 1.,
_speaking ? 1. : 0.,
st::widgetFadeDuration);
if (!_speaking
|| (_state == State::MutedByMe)
|| (_state == State::Muted)) {
_statusIcon = nullptr;
} else if (!_statusIcon) {
_statusIcon = std::make_unique<StatusIcon>(
(float)_volume / Group::kMaxVolume);
_statusIcon->arcs->setStrokeRatio(kArcsStrokeRatio);
_statusIcon->arcsWidth = _statusIcon->arcs->finishedWidth();
const auto wasArcsWidth = _statusIcon->lifetime.make_state<int>(0);
_statusIcon->arcs->startUpdateRequests(
) | rpl::start_with_next([=] {
if (!_arcsAnimation.animating()) {
*wasArcsWidth = _statusIcon->arcsWidth;
}
auto callback = [=](float64 value) {
if (_statusIcon) {
_statusIcon->arcs->update(crl::now());
_statusIcon->arcsWidth = anim::interpolate(
*wasArcsWidth,
_statusIcon->arcs->finishedWidth(),
value);
}
_delegate->rowUpdateRow(this);
};
_arcsAnimation.start(
std::move(callback),
0.,
1.,
st::groupCallSpeakerArcsAnimation.duration);
}, _statusIcon->lifetime);
}
}
void Row::setSounding(bool sounding) {
@@ -382,6 +478,13 @@ void Row::setSsrc(uint32 ssrc) {
_ssrc = ssrc;
}
void Row::setVolume(int volume) {
_volume = volume;
if (_statusIcon) {
_statusIcon->arcs->setValue((float)volume / Group::kMaxVolume);
}
}
void Row::updateLevel(float level) {
Expects(_blobsAnimation != nullptr);
@@ -449,13 +552,16 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
auto userpic = ensureUserpicView();
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
if (_blobsAnimation) {
const auto mutedByMe = (_state == State::MutedByMe);
const auto shift = QPointF(x + size / 2., y + size / 2.);
auto hq = PainterHighQualityEnabler(p);
p.translate(shift);
const auto brush = anim::brush(
st::groupCallMemberInactiveStatus,
st::groupCallMemberActiveStatus,
_speakingAnimation.value(_speaking ? 1. : 0.));
const auto brush = mutedByMe
? st::groupCallMemberMutedIcon->b
: anim::brush(
st::groupCallMemberInactiveStatus,
st::groupCallMemberActiveStatus,
_speakingAnimation.value(_speaking ? 1. : 0.));
_blobsAnimation->blobs.paint(p, brush);
p.translate(-shift);
p.setOpacity(1.);
@@ -492,6 +598,60 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
};
}
int Row::statusIconWidth() const {
if (!_statusIcon) {
return 0;
}
return _speaking
? (_statusIcon->speaker.width() + _statusIcon->arcsWidth)
: 0;
}
int Row::statusIconHeight() const {
if (!_statusIcon) {
return 0;
}
return _speaking
? _statusIcon->speaker.height()
: 0;
}
void Row::paintStatusIcon(
Painter &p,
const style::PeerListItem &st,
const style::font &font,
bool selected) {
if (!_statusIcon) {
return;
}
p.setFont(font);
const auto color = (_speaking
? st.statusFgActive
: (selected ? st.statusFgOver : st.statusFg))->c;
p.setPen(color);
const auto speakerRect = QRect(
st.statusPosition
+ QPoint(0, (font->height - statusIconHeight()) / 2),
_statusIcon->speaker.size());
const auto arcPosition = speakerRect.topLeft()
+ QPoint(
speakerRect.width() - st::groupCallStatusSpeakerArcsSkip,
speakerRect.height() / 2);
const auto volume = std::round(_volume / 100.);
_statusIcon->speaker.paint(
p,
speakerRect.topLeft(),
speakerRect.width(),
color);
p.save();
p.translate(arcPosition);
_statusIcon->arcs->paint(p, color);
p.restore();
}
void Row::paintStatusText(
Painter &p,
const style::PeerListItem &st,
@@ -500,24 +660,36 @@ void Row::paintStatusText(
int availableWidth,
int outerWidth,
bool selected) {
if (_state != State::Invited) {
p.save();
const auto &font = st::normalFont;
paintStatusIcon(p, st, font, selected);
const auto translatedWidth = statusIconWidth();
p.translate(translatedWidth, 0);
const auto guard = gsl::finally([&] { p.restore(); });
if (_state != State::Invited && _state != State::MutedByMe) {
PeerListRow::paintStatusText(
p,
st,
x,
y,
availableWidth,
availableWidth - translatedWidth,
outerWidth,
selected);
return;
}
p.setFont(st::normalFont);
p.setPen(st::groupCallMemberNotJoinedStatus);
p.setFont(font);
if (_state == State::MutedByMe) {
p.setPen(st::groupCallMemberMutedIcon);
} else {
p.setPen(st::groupCallMemberNotJoinedStatus);
}
p.drawTextLeft(
x,
y,
outerWidth,
(peer()->isSelf()
(_state == State::MutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now)
: peer()->isSelf()
? tr::lng_status_connecting(tr::now)
: tr::lng_group_call_invited_status(tr::now)));
}
@@ -559,13 +731,16 @@ void Row::paintAction(
(_state == State::Active) ? 1. : 0.);
const auto muted = _mutedAnimation.value(
(_state == State::Muted) ? 1. : 0.);
_delegate->rowPaintIcon(p, iconRect, speaking, active, muted);
const auto mutedByMe = (_state == State::MutedByMe);
_delegate->rowPaintIcon(p, iconRect, speaking, active, muted, mutedByMe);
}
void Row::refreshStatus() {
setCustomStatus(
(_speaking
? tr::lng_group_call_active(tr::now)
? u"%1% %2"_q
.arg(std::round(_volume / 100.))
.arg(tr::lng_group_call_active(tr::now))
: tr::lng_group_call_inactive(tr::now)),
_speaking);
}
@@ -682,9 +857,12 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
_realCallRawValue = real;
_realId = real->id();
_fullCount = real->fullCountValue(
) | rpl::map([](int value) {
return std::max(value, 1);
_fullCount = rpl::combine(
real->fullCountValue(),
_fullCountMin.value(),
_fullCountMax.value()
) | rpl::map([](int value, int min, int max) {
return std::max(std::clamp(value, min, max), 1);
});
real->participantsSliceAdded(
@@ -741,32 +919,48 @@ void MembersController::appendInvitedUsers() {
void MembersController::updateRow(
const std::optional<Data::GroupCall::Participant> &was,
const Data::GroupCall::Participant &now) {
auto reorderIfInvitedBeforeIndex = 0;
auto countChange = 0;
if (const auto row = findRow(now.user)) {
if (now.speaking && (!was || !was->speaking)) {
checkSpeakingRowPosition(row);
}
if (row->state() == Row::State::Invited) {
reorderIfInvitedBeforeIndex = row->absoluteIndex();
countChange = 1;
}
updateRow(row, &now);
} else if (auto row = createRow(now)) {
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = delegate()->peerListFullRowsCount();
if (!count) {
return false;
}
const auto row = delegate()->peerListRowAt(count - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
reorderIfInvitedBeforeIndex = delegate()->peerListFullRowsCount();
delegate()->peerListAppendRow(std::move(row));
if (reorder) {
delegate()->peerListPartitionRows([](const PeerListRow &row) {
return static_cast<const Row&>(row).state() != kInvited;
});
}
}
delegate()->peerListRefreshRows();
countChange = 1;
}
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = reorderIfInvitedBeforeIndex;
if (count <= 0) {
return false;
}
const auto row = delegate()->peerListRowAt(
reorderIfInvitedBeforeIndex - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
if (reorder) {
delegate()->peerListPartitionRows([](const PeerListRow &row) {
return static_cast<const Row&>(row).state() != kInvited;
});
}
if (countChange) {
const auto fullCountMin = _fullCountMin.current() + countChange;
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
}
}
@@ -879,10 +1073,11 @@ void MembersController::prepare() {
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
const auto call = _call.get();
if (const auto real = _peer->groupCall();
real && call && real->id() == call->id()) {
if (const auto real = _peer->groupCall()
; real && call && real->id() == call->id()) {
prepareRows(real);
} else if (auto row = createSelfRow()) {
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
}
@@ -898,6 +1093,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
auto foundSelf = false;
auto changed = false;
const auto &participants = real->participants();
auto fullCountMin = 0;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
@@ -912,6 +1108,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
not_null{ user },
&Data::GroupCall::Participant::user);
if (contains) {
++fullCountMin;
++i;
} else {
changed = true;
@@ -927,18 +1124,29 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
&Data::GroupCall::Participant::user);
auto row = (i != end(participants)) ? createRow(*i) : createSelfRow();
if (row) {
if (row->state() != Row::State::Invited) {
++fullCountMin;
}
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
for (const auto &participant : participants) {
if (auto row = createRow(participant)) {
++fullCountMin;
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
if (changed) {
delegate()->peerListRefreshRows();
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
if (real->participantsLoaded()) {
_fullCountMax = fullCountMin;
}
}
}
@@ -953,6 +1161,11 @@ auto MembersController::toggleMuteRequests() const
return _toggleMuteRequests.events();
}
auto MembersController::changeVolumeRequests() const
-> rpl::producer<VolumeRequest> {
return _changeVolumeRequests.events();
}
bool MembersController::rowCanMuteMembers() {
return _peer->canManageGroupCall();
}
@@ -966,11 +1179,12 @@ void MembersController::rowPaintIcon(
QRect rect,
float64 speaking,
float64 active,
float64 muted) {
float64 muted,
bool mutedByMe) {
const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon;
const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2;
const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2;
if (speaking == 1.) {
if (speaking == 1. && !mutedByMe) {
// Just green icon, no cross, no coloring.
greenIcon.paintInCenter(p, rect);
return;
@@ -998,7 +1212,9 @@ void MembersController::rowPaintIcon(
}
const auto activeInactiveColor = anim::color(
st::groupCallMemberInactiveIcon,
st::groupCallMemberActiveIcon,
(mutedByMe
? st::groupCallMemberMutedIcon
: st::groupCallMemberActiveIcon),
speaking);
const auto iconColor = anim::color(
activeInactiveColor,
@@ -1088,15 +1304,6 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
}
return false;
}();
const auto mute = admin
? (muteState == Row::State::Active)
: (muteState != Row::State::Muted);
const auto toggleMute = crl::guard(this, [=] {
_toggleMuteRequests.fire(MuteRequest{
.user = user,
.mute = mute,
});
});
const auto session = &user->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* {
@@ -1147,15 +1354,10 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
_kickMemberRequests.fire_copy(user);
});
if ((muteState != Row::State::Invited)
&& _peer->canManageGroupCall()
&& (!admin || mute)) {
result->addAction(
(mute
? tr::lng_group_call_context_mute(tr::now)
: tr::lng_group_call_context_unmute(tr::now)),
toggleMute);
if (real->ssrc() != 0) {
addMuteActionsToContextMenu(result, user, real);
}
result->addAction(
tr::lng_context_view_profile(tr::now),
showProfile);
@@ -1181,6 +1383,111 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
return result;
}
void MembersController::addMuteActionsToContextMenu(
not_null<Ui::PopupMenu*> menu,
not_null<UserData*> user,
not_null<Row*> row) {
const auto muteString = [=] {
return (_peer->canManageGroupCall()
? tr::lng_group_call_context_mute
: tr::lng_group_call_context_mute_for_me)(tr::now);
};
const auto unmuteString = [=] {
return (_peer->canManageGroupCall()
? tr::lng_group_call_context_unmute
: tr::lng_group_call_context_unmute_for_me)(tr::now);
};
const auto toggleMute = crl::guard(this, [=](bool mute, bool local) {
_toggleMuteRequests.fire(Group::MuteRequest{
.user = user,
.mute = mute,
.locallyOnly = local,
});
});
const auto changeVolume = crl::guard(this, [=](
int volume,
bool local) {
_changeVolumeRequests.fire(Group::VolumeRequest{
.user = user,
.volume = std::clamp(volume, 1, Group::kMaxVolume),
.locallyOnly = local,
});
});
const auto muteState = row->state();
const auto isMuted = (muteState == Row::State::Muted)
|| (muteState == Row::State::MutedByMe);
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
if (!isMuted) {
const auto call = _call.get();
auto otherParticipantStateValue = call
? call->otherParticipantStateValue(
) | rpl::filter([=](const Group::ParticipantState &data) {
return data.user == user;
})
: rpl::never<Group::ParticipantState>() | rpl::type_erased();
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
menu,
st::groupCallPopupMenu.menu,
otherParticipantStateValue,
row->volume(),
Group::kMaxVolume,
isMuted);
mutesFromVolume = volumeItem->toggleMuteRequests();
volumeItem->toggleMuteRequests(
) | rpl::start_with_next([=](bool muted) {
toggleMute(muted, false);
}, volumeItem->lifetime());
volumeItem->toggleMuteLocallyRequests(
) | rpl::start_with_next([=](bool muted) {
toggleMute(muted, true);
}, volumeItem->lifetime());
volumeItem->changeVolumeRequests(
) | rpl::start_with_next([=](int volume) {
changeVolume(volume, false);
}, volumeItem->lifetime());
volumeItem->changeVolumeLocallyRequests(
) | rpl::start_with_next([=](int volume) {
changeVolume(volume, true);
}, volumeItem->lifetime());
menu->addAction(std::move(volumeItem));
};
const auto muteAction = [&]() -> QAction* {
if (muteState == Row::State::Invited) {
return nullptr;
}
auto callback = [=] {
const auto state = row->state();
const auto muted = (state == Row::State::Muted)
|| (state == Row::State::MutedByMe);
toggleMute(!muted, false);
};
return menu->addAction(
isMuted ? unmuteString() : muteString(),
std::move(callback));
}();
if (muteAction) {
std::move(
mutesFromVolume
) | rpl::start_with_next([=](bool muted) {
muteAction->setText(muted ? unmuteString() : muteString());
}, menu->lifetime());
}
}
std::unique_ptr<Row> MembersController::createSelfRow() {
const auto self = _peer->session().user();
auto result = std::make_unique<Row>(this, self);
@@ -1207,6 +1514,7 @@ std::unique_ptr<Row> MembersController::createInvitedRow(
} // namespace
GroupMembers::GroupMembers(
not_null<QWidget*> parent,
not_null<GroupCall*> call)
@@ -1222,11 +1530,17 @@ GroupMembers::GroupMembers(
}
auto GroupMembers::toggleMuteRequests() const
-> rpl::producer<GroupMembers::MuteRequest> {
-> rpl::producer<Group::MuteRequest> {
return static_cast<MembersController*>(
_listController.get())->toggleMuteRequests();
}
auto GroupMembers::changeVolumeRequests() const
-> rpl::producer<Group::VolumeRequest> {
return static_cast<MembersController*>(
_listController.get())->changeVolumeRequests();
}
auto GroupMembers::kickMemberRequests() const
-> rpl::producer<not_null<UserData*>> {
return static_cast<MembersController*>(

View File

@@ -20,6 +20,11 @@ class GroupCall;
namespace Calls {
namespace Group {
struct VolumeRequest;
struct MuteRequest;
} // namespace Group
class GroupCall;
class GroupMembers final
@@ -30,15 +35,13 @@ public:
not_null<QWidget*> parent,
not_null<GroupCall*> call);
struct MuteRequest {
not_null<UserData*> user;
bool mute = false;
};
[[nodiscard]] int desiredHeight() const;
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
[[nodiscard]] auto toggleMuteRequests() const
-> rpl::producer<Group::MuteRequest>;
[[nodiscard]] auto changeVolumeRequests() const
-> rpl::producer<Group::VolumeRequest>;
[[nodiscard]] auto kickMemberRequests() const
-> rpl::producer<not_null<UserData*>>;
[[nodiscard]] rpl::producer<> addMembersRequests() const {

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_group_panel.h"
#include "calls/calls_group_common.h"
#include "calls/calls_group_members.h"
#include "calls/calls_group_settings.h"
#include "ui/widgets/buttons.h"
@@ -505,9 +506,16 @@ void GroupPanel::initWithCall(GroupCall *call) {
}, _callLifetime);
_members->toggleMuteRequests(
) | rpl::start_with_next([=](GroupMembers::MuteRequest request) {
) | rpl::start_with_next([=](Group::MuteRequest request) {
if (_call) {
_call->toggleMute(request.user, request.mute);
_call->toggleMute(request);
}
}, _callLifetime);
_members->changeVolumeRequests(
) | rpl::start_with_next([=](Group::VolumeRequest request) {
if (_call) {
_call->changeVolume(request);
}
}, _callLifetime);

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_group_call.h"
#include "data/data_changes.h"
#include "core/application.h"
#include "boxes/single_choice_box.h"
#include "webrtc/webrtc_audio_input_tester.h"
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_calls.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "styles/style_layers.h"
#include "styles/style_calls.h"
#include "styles/style_settings.h"
@@ -441,20 +443,9 @@ void GroupCallSettingsBox(
)->addClickHandler([=] {
if (!copyLink() && !state->generatingLink) {
state->generatingLink = true;
peer->session().api().request(MTPmessages_ExportChatInvite(
peer->input
)).done([=](const MTPExportedChatInvite &result) {
if (result.type() == mtpc_chatInviteExported) {
const auto link = qs(
result.c_chatInviteExported().vlink());
if (const auto chat = peer->asChat()) {
chat->setInviteLink(link);
} else if (const auto channel = peer->asChannel()) {
channel->setInviteLink(link);
}
copyLink();
}
}).send();
peer->session().api().inviteLinks().create(
peer,
crl::guard(layout, [=](auto&&) { copyLink(); }));
}
});
}
@@ -480,6 +471,7 @@ void GroupCallSettingsBox(
// Means we finished showing the box.
crl::on_main(box, [=] {
state->micTester = std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
});

View File

@@ -377,6 +377,8 @@ void Instance::handleCallUpdate(
} else if (phoneCall.vdate().v + (config.callRingTimeoutMs / 1000)
< base::unixtime::now()) {
LOG(("Ignoring too old call."));
} else if (Core::App().settings().disableCalls()) {
LOG(("Ignoring call because of 'accept calls' settings."));
} else {
createCall(user, Call::Type::Incoming, phoneCall.is_video());
_currentCall->handleUpdate(call);

View File

@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/platform_specific.h"
#include "base/platform/base_platform_info.h"
#include "window/main_window.h"
#include "media/view/media_view_pip.h" // Utilities for frame rotation.
#include "app.h"
#include "webrtc/webrtc_video_track.h"
#include "styles/style_calls.h"
@@ -98,12 +99,26 @@ Panel::Incoming::Incoming(
void Panel::Incoming::paintEvent(QPaintEvent *e) {
QPainter p(this);
const auto frame = _track->frame(Webrtc::FrameRequest());
if (frame.isNull()) {
const auto [image, rotation] = _track->frameOriginalWithRotation();
if (image.isNull()) {
p.fillRect(e->rect(), Qt::black);
} else {
using namespace Media::View;
auto hq = PainterHighQualityEnabler(p);
p.drawImage(rect(), frame);
if (UsePainterRotation(rotation)) {
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(RotatedRect(rect(), rotation), image);
if (rotation) {
p.restore();
}
} else if (rotation) {
p.drawImage(rect(), RotateFrameImage(image, rotation));
} else {
p.drawImage(rect(), image);
}
fillBottomShadow(p);
fillTopShadow(p);
}
@@ -462,7 +477,11 @@ void Panel::reinitWithCall(Call *call) {
_call->videoIncoming()->renderNextFrame(
) | rpl::start_with_next([=] {
setIncomingSize(_call->videoIncoming()->frame({}).size());
const auto track = _call->videoIncoming();
const auto [frame, rotation] = track->frameOriginalWithRotation();
setIncomingSize((rotation == 90 || rotation == 270)
? QSize(frame.height(), frame.width())
: frame.size());
if (_incoming->isHidden()) {
return;
}

View File

@@ -0,0 +1,306 @@
/*
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 "calls/calls_volume_item.h"
#include "calls/calls_group_common.h"
#include "ui/effects/animation_value.h"
#include "ui/effects/cross_line.h"
#include "ui/widgets/continuous_sliders.h"
#include "styles/style_calls.h"
#include "ui/paint/arcs.h"
namespace Calls {
namespace {
constexpr auto kMaxVolumePercent = 200;
const auto kSpeakerThreshold = std::vector<float>{
10.0f / kMaxVolumePercent,
50.0f / kMaxVolumePercent,
150.0f / kMaxVolumePercent };
constexpr auto kVolumeStickedValues =
std::array<std::pair<float64, float64>, 7>{{
{ 25. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 50. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 75. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 100. / kMaxVolumePercent, 5. / kMaxVolumePercent },
{ 125. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 150. / kMaxVolumePercent, 2. / kMaxVolumePercent },
{ 175. / kMaxVolumePercent, 2. / kMaxVolumePercent },
}};
QString VolumeString(int volumePercent) {
return u"%1%"_q.arg(volumePercent);
}
} // namespace
MenuVolumeItem::MenuVolumeItem(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<Group::ParticipantState> participantState,
int startVolume,
int maxVolume,
bool muted)
: Ui::Menu::ItemBase(parent, st)
, _maxVolume(maxVolume)
, _cloudMuted(muted)
, _localMuted(muted)
, _slider(base::make_unique_q<Ui::MediaSlider>(
this,
st::groupCallMenuVolumeSlider))
, _dummyAction(new QAction(parent))
, _st(st)
, _stCross(st::groupCallMuteCrossLine)
, _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
, _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
st::groupCallSpeakerArcsAnimation,
kSpeakerThreshold,
_localMuted ? 0. : (startVolume / float(maxVolume)),
Ui::Paint::ArcsAnimation::HorizontalDirection::Right)) {
initResizeHook(parent->sizeValue());
enableMouseSelecting();
enableMouseSelecting(_slider.get());
_slider->setAlwaysDisplayMarker(true);
sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size);
_itemRect = geometry - _st.itemPadding;
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
_arcPosition = _speakerRect.center()
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
_volumeRect = QRect(
_arcPosition.x()
+ st::groupCallMenuVolumeSkip
+ _arcs->finishedWidth(),
_speakerRect.y(),
_st.itemStyle.font->width(VolumeString(kMaxVolumePercent)),
_speakerRect.height());
_slider->setGeometry(_itemRect
- style::margins(0, contentHeight() / 2, 0, 0));
}, lifetime());
setCloudVolume(startVolume);
paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
Painter p(this);
const auto volume = _localMuted
? 0
: std::round(_slider->value() * kMaxVolumePercent);
const auto muteProgress =
_crossLineAnimation.value((!volume) ? 1. : 0.);
const auto selected = isSelected();
p.fillRect(clip, selected ? st.itemBgOver : st.itemBg);
const auto mutePen = anim::color(
unmuteColor(),
muteColor(),
muteProgress);
p.setPen(mutePen);
p.setFont(_st.itemStyle.font);
p.drawText(_volumeRect, VolumeString(volume), style::al_left);
_crossLineMute->paint(
p,
_speakerRect.topLeft(),
muteProgress,
(!muteProgress) ? std::nullopt : std::optional<QColor>(mutePen));
{
p.translate(_arcPosition);
_arcs->paint(p);
}
}, lifetime());
_slider->setChangeProgressCallback([=](float64 value) {
const auto newMuted = (value == 0);
if (_localMuted != newMuted) {
_localMuted = newMuted;
_toggleMuteLocallyRequests.fire_copy(newMuted);
_crossLineAnimation.start(
[=] { update(_speakerRect.united(_volumeRect)); },
_localMuted ? 0. : 1.,
_localMuted ? 1. : 0.,
st::callPanelDuration);
}
if (value > 0) {
_changeVolumeLocallyRequests.fire(value * _maxVolume);
}
update(_volumeRect);
_arcs->setValue(value);
});
const auto returnVolume = [=] {
_changeVolumeLocallyRequests.fire_copy(_cloudVolume);
};
_slider->setChangeFinishedCallback([=](float64 value) {
const auto newVolume = std::round(value * _maxVolume);
const auto muted = (value == 0);
if (!_cloudMuted && muted) {
returnVolume();
_localMuted = true;
_toggleMuteRequests.fire(true);
}
if (_cloudMuted && muted) {
returnVolume();
}
if (_cloudMuted && !muted) {
_waitingForUpdateVolume = true;
_localMuted = false;
_toggleMuteRequests.fire(false);
}
if (!_cloudMuted && !muted) {
_changeVolumeRequests.fire_copy(newVolume);
}
});
std::move(
participantState
) | rpl::start_with_next([=](const Group::ParticipantState &state) {
const auto newMuted = state.mutedByMe;
const auto newVolume = state.volume.value_or(0);
_cloudMuted = _localMuted = newMuted;
if (!newVolume) {
return;
}
if (_waitingForUpdateVolume) {
const auto localVolume =
std::round(_slider->value() * _maxVolume);
if ((localVolume != newVolume)
&& (_cloudVolume == newVolume)) {
_changeVolumeRequests.fire(int(localVolume));
}
} else {
setCloudVolume(newVolume);
}
_waitingForUpdateVolume = false;
}, lifetime());
_slider->setAdjustCallback([=](float64 value) {
for (const auto &snap : kVolumeStickedValues) {
if (value > (snap.first - snap.second)
&& value < (snap.first + snap.second)) {
return snap.first;
}
}
return value;
});
initArcsAnimation();
}
void MenuVolumeItem::initArcsAnimation() {
const auto volumeLeftWas = lifetime().make_state<int>(0);
const auto lastTime = lifetime().make_state<int>(0);
_arcsAnimation.init([=](crl::time now) {
_arcs->update(now);
update(_speakerRect);
const auto wasRect = _volumeRect;
_volumeRect.moveLeft(anim::interpolate(
*volumeLeftWas,
_arcPosition.x()
+ st::groupCallMenuVolumeSkip
+ _arcs->finishedWidth(),
std::clamp(
(now - (*lastTime))
/ float64(st::groupCallSpeakerArcsAnimation.duration),
0.,
1.)));
update(_speakerRect.united(wasRect.united(_volumeRect)));
});
_arcs->startUpdateRequests(
) | rpl::start_with_next([=] {
if (!_arcsAnimation.animating()) {
*volumeLeftWas = _volumeRect.left();
*lastTime = crl::now();
_arcsAnimation.start();
}
}, lifetime());
_arcs->stopUpdateRequests(
) | rpl::start_with_next([=] {
_arcsAnimation.stop();
}, lifetime());
}
QColor MenuVolumeItem::unmuteColor() const {
return (isSelected()
? _st.itemFgOver
: isEnabled()
? _st.itemFg
: _st.itemFgDisabled)->c;
}
QColor MenuVolumeItem::muteColor() const {
return (isSelected()
? st::attentionButtonFgOver
: st::attentionButtonFg)->c;
}
void MenuVolumeItem::setCloudVolume(int volume) {
if (_cloudVolume == volume) {
return;
}
_cloudVolume = volume;
if (!_slider->isChanging()) {
setSliderVolume(_cloudMuted ? 0. : volume);
}
}
void MenuVolumeItem::setSliderVolume(int volume) {
_slider->setValue(float64(volume) / _maxVolume);
update(_volumeRect);
}
not_null<QAction*> MenuVolumeItem::action() const {
return _dummyAction;
}
bool MenuVolumeItem::isEnabled() const {
return true;
}
int MenuVolumeItem::contentHeight() const {
return _st.itemPadding.top()
+ _st.itemPadding.bottom()
+ _stCross.icon.height() * 2;
}
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
return _toggleMuteRequests.events();
}
rpl::producer<bool> MenuVolumeItem::toggleMuteLocallyRequests() const {
return _toggleMuteLocallyRequests.events();
}
rpl::producer<int> MenuVolumeItem::changeVolumeRequests() const {
return _changeVolumeRequests.events();
}
rpl::producer<int> MenuVolumeItem::changeVolumeLocallyRequests() const {
return _changeVolumeLocallyRequests.events();
}
} // namespace Calls

View File

@@ -0,0 +1,87 @@
/*
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/rp_widget.h"
#include "ui/widgets/menu/menu_item_base.h"
namespace Ui {
class CrossLineAnimation;
class MediaSlider;
namespace Paint {
class ArcsAnimation;
} // namespace Paint
} // namespace Ui
namespace Calls {
namespace Group {
struct MuteRequest;
struct VolumeRequest;
struct ParticipantState;
} // namespace Group
class MenuVolumeItem final : public Ui::Menu::ItemBase {
public:
MenuVolumeItem(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<Group::ParticipantState> participantState,
int startVolume,
int maxVolume,
bool muted);
not_null<QAction*> action() const override;
bool isEnabled() const override;
[[nodiscard]] rpl::producer<bool> toggleMuteRequests() const;
[[nodiscard]] rpl::producer<bool> toggleMuteLocallyRequests() const;
[[nodiscard]] rpl::producer<int> changeVolumeRequests() const;
[[nodiscard]] rpl::producer<int> changeVolumeLocallyRequests() const;
protected:
int contentHeight() const override;
private:
void initArcsAnimation();
void setCloudVolume(int volume);
void setSliderVolume(int volume);
QColor unmuteColor() const;
QColor muteColor() const;
const int _maxVolume;
int _cloudVolume = 0;
bool _waitingForUpdateVolume = false;
bool _cloudMuted = false;
bool _localMuted = false;
QRect _itemRect;
QRect _speakerRect;
QRect _volumeRect;
QPoint _arcPosition;
const base::unique_qptr<Ui::MediaSlider> _slider;
const not_null<QAction*> _dummyAction;
const style::Menu &_st;
const style::CrossLineAnimation &_stCross;
const std::unique_ptr<Ui::CrossLineAnimation> _crossLineMute;
Ui::Animations::Simple _crossLineAnimation;
const std::unique_ptr<Ui::Paint::ArcsAnimation> _arcs;
Ui::Animations::Basic _arcsAnimation;
rpl::event_stream<bool> _toggleMuteRequests;
rpl::event_stream<bool> _toggleMuteLocallyRequests;
rpl::event_stream<int> _changeVolumeRequests;
rpl::event_stream<int> _changeVolumeLocallyRequests;
};
} // namespace Calls

View File

@@ -179,12 +179,15 @@ void SuggestionsWidget::scrollByWheelEvent(not_null<QWheelEvent*> e) {
const auto delta = e->pixelDelta().x()
? e->pixelDelta().x()
: e->angleDelta().x();
return snap(current - ((rtl() ? -1 : 1) * delta), 0, _scrollMax);
return std::clamp(
current - ((rtl() ? -1 : 1) * delta),
0,
_scrollMax);
} else if (vertical) {
const auto delta = e->pixelDelta().y()
? e->pixelDelta().y()
: e->angleDelta().y();
return snap(current - delta, 0, _scrollMax);
return std::clamp(current - delta, 0, _scrollMax);
}
return current;
}();
@@ -241,7 +244,7 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) {
void SuggestionsWidget::paintFadings(Painter &p) const {
const auto scroll = scrollCurrent();
const auto o_left = snap(
const auto o_left = std::clamp(
scroll / float64(st::emojiSuggestionsFadeAfter),
0.,
1.);
@@ -256,7 +259,7 @@ void SuggestionsWidget::paintFadings(Painter &p) const {
st::emojiSuggestionsFadeLeft.fill(p, rect);
p.setOpacity(1.);
}
const auto o_right = snap(
const auto o_right = std::clamp(
(_scrollMax - scroll) / float64(st::emojiSuggestionsFadeAfter),
0.,
1.);
@@ -422,7 +425,7 @@ void SuggestionsWidget::mouseMoveEvent(QMouseEvent *e) {
const auto globalPosition = e->globalPos();
if (_dragScrollStart >= 0) {
const auto delta = (_mousePressPosition.x() - globalPosition.x());
const auto scroll = snap(
const auto scroll = std::clamp(
_dragScrollStart + (rtl() ? -1 : 1) * delta,
0,
_scrollMax);

View File

@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
#include "base/unixtime.h"
#include "base/openssl_help.h"
#include "window/window_session_controller.h"
#include "facades.h"
#include "styles/style_chat.h"
@@ -636,7 +637,7 @@ void FieldAutocomplete::showAnimated() {
return;
}
if (_cache.isNull()) {
_stickersSeed = rand_value<uint64>();
_stickersSeed = openssl::RandomValue<uint64>();
_scroll->show();
_cache = Ui::GrabWidget(this);
}
@@ -1138,7 +1139,7 @@ void FieldAutocomplete::Inner::contextMenuEvent(QContextMenuEvent *e) {
SendMenu::DefaultSilentCallback(send),
SendMenu::DefaultScheduleCallback(this, type, send));
if (!_menu->actions().empty()) {
if (!_menu->empty()) {
_menu->popup(QCursor::pos());
}
}

View File

@@ -404,7 +404,7 @@ void StickersListWidget::Footer::setSelectedIcon(
auto iconsCountForCentering = (2 * _iconSel + 1);
auto iconsWidthForCentering = iconsCountForCentering
* st::stickerIconWidth;
auto iconsXFinal = snap(
auto iconsXFinal = std::clamp(
(_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2,
0,
_iconsMax);
@@ -486,13 +486,19 @@ void StickersListWidget::Footer::paintSelectionBar(Painter &p) const {
}
void StickersListWidget::Footer::paintLeftRightFading(Painter &p) const {
auto o_left = snap(_iconsX.current() / st::stickerIconLeft.width(), 0., 1.);
auto o_left = std::clamp(
_iconsX.current() / st::stickerIconLeft.width(),
0.,
1.);
if (o_left > 0) {
p.setOpacity(o_left);
st::stickerIconLeft.fill(p, style::rtlrect(_iconsLeft, _iconsTop, st::stickerIconLeft.width(), st::emojiFooterHeight, width()));
p.setOpacity(1.);
}
auto o_right = snap((_iconsMax - _iconsX.current()) / st::stickerIconRight.width(), 0., 1.);
auto o_right = std::clamp(
(_iconsMax - _iconsX.current()) / st::stickerIconRight.width(),
0.,
1.);
if (o_right > 0) {
p.setOpacity(o_right);
st::stickerIconRight.fill(p, style::rtlrect(width() - _iconsRight - st::stickerIconRight.width(), _iconsTop, st::stickerIconRight.width(), st::emojiFooterHeight, width()));
@@ -554,7 +560,11 @@ void StickersListWidget::Footer::mouseMoveEvent(QMouseEvent *e) {
}
}
if (_iconsDragging) {
auto newX = snap(_iconsStartX + (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x()), 0, _iconsMax);
auto newX = std::clamp(
(rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x())
+ _iconsStartX,
0,
_iconsMax);
if (newX != qRound(_iconsX.current())) {
_iconsX = anim::value(newX, newX);
_iconsStartAnim = 0;
@@ -589,7 +599,10 @@ void StickersListWidget::Footer::mouseReleaseEvent(QMouseEvent *e) {
}
void StickersListWidget::Footer::finishDragging() {
auto newX = snap(_iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(), 0, _iconsMax);
auto newX = std::clamp(
_iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(),
0,
_iconsMax);
if (newX != qRound(_iconsX.current())) {
_iconsX = anim::value(newX, newX);
_iconsStartAnim = 0;
@@ -621,9 +634,19 @@ void StickersListWidget::Footer::scrollByWheelEvent(
}
auto newX = qRound(_iconsX.current());
if (/*_horizontal && */horizontal) {
newX = snap(newX - (rtl() ? -1 : 1) * (e->pixelDelta().x() ? e->pixelDelta().x() : e->angleDelta().x()), 0, _iconsMax);
newX = std::clamp(
newX - (rtl() ? -1 : 1) * (e->pixelDelta().x()
? e->pixelDelta().x()
: e->angleDelta().x()),
0,
_iconsMax);
} else if (/*!_horizontal && */vertical) {
newX = snap(newX - (e->pixelDelta().y() ? e->pixelDelta().y() : e->angleDelta().y()), 0, _iconsMax);
newX = std::clamp(
newX - (e->pixelDelta().y()
? e->pixelDelta().y()
: e->angleDelta().y()),
0,
_iconsMax);
}
if (newX != qRound(_iconsX.current())) {
_iconsX = anim::value(newX, newX);

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