Compare commits

...

256 Commits

Author SHA1 Message Date
John Preston
b3660f1ed8 Version 2.5.9: Move window position to Core::Settings. 2021-02-17 19:20:05 +04:00
John Preston
70570e0987 Always make sure that settings are saved. 2021-02-17 19:19:00 +04:00
John Preston
baccec623d Allow opening large (> 512px) stickers in media viewer. 2021-02-17 19:15:49 +04:00
John Preston
093d89db83 Fix display of stickers in media viewer. 2021-02-17 19:13:37 +04:00
John Preston
48fea47d16 Version 2.5.9: Fix build for Microsoft Store. 2021-02-17 17:33:24 +04:00
John Preston
8500bf6073 Version 2.5.9: Fix reading settings from old apps. 2021-02-17 15:58:57 +04:00
John Preston
69b41fadef Version 2.5.9.
- Add 'Invite via Link' button to Add Members box.
- Fix window size in Windows 10 Tablet Mode.
- Fix layout of round video messages in channels.
2021-02-17 11:19:45 +04:00
Ilya Fedin
d44f076f0b Take in account device pixel ratio when setting window extents 2021-02-17 11:16:00 +04:00
Ilya Fedin
3637fec397 Ensure controls aren't duplictated 2021-02-17 11:16:00 +04:00
Ilya Fedin
e109da037e Add a private method to get control widget by enum to TitleWidgetQt 2021-02-17 11:16:00 +04:00
Nicholas Guriev
3a659b4b54 Add -mxgot flag to td_scheme on MIPS64
This fixes "relocation truncated to fit ..." error on final linking. It should
not introduce a lot of overhead since the code is used only in network
communications.

This errors appears sometimes and I do not know exact conditions, I think it is
related to large size of an object file with the schema.

More docs see at https://gcc.gnu.org/onlinedocs/gcc/MIPS-Options.html#index-mxgot-1
2021-02-17 11:15:31 +04:00
Ilya Fedin
7e4dff25e9 Log media viewer geometry 2021-02-17 11:15:16 +04:00
Ilya Fedin
da74fe4248 Call moveToScreen right before showFullScreen for Wayland 2021-02-17 11:15:16 +04:00
Ilya Fedin
294f849775 Init last path with gtk dialog 2021-02-17 11:14:41 +04:00
Ilya Fedin
88951e9e5c Fix saving last path in confined environments 2021-02-17 11:14:41 +04:00
Il'ya
153b91248d set nproc for number of jobs to build webrtc in docker 2021-02-17 11:14:13 +04:00
Ilya Fedin
7977331d8b Read DESKTOPINTEGRATION variable instead of TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION
Since it's widely used (by AppImages, for instance)
2021-02-17 11:12:06 +04:00
John Preston
8ec60e0321 Show all .webp as stickers.
Allow opening stickers not from stickerpacks in media viewer.
2021-02-17 10:46:36 +04:00
John Preston
dcebefe2bb Fix layout of round video messages. 2021-02-17 10:46:36 +04:00
John Preston
93a88b54ad Fix sending images from clipboard.
Regression was introduced in ce1b94eb1.
2021-02-17 10:46:36 +04:00
John Preston
07f94cc184 Fix skin-colored animated emoji refresh. 2021-02-17 10:46:36 +04:00
John Preston
9a0edbd0c5 Update skin-colored animated emoji. 2021-02-17 10:46:36 +04:00
John Preston
c935f1bb16 Always show check marks on outgoing service messages. 2021-02-17 10:46:36 +04:00
John Preston
3cd05a34d9 In legacy groups admins can't remove admins. 2021-02-17 10:46:36 +04:00
John Preston
223681d2da Fix default chat rights for creator. 2021-02-17 10:46:36 +04:00
23rd
c3c1759f3c Fixed updating number of sessions after terminating any of them. 2021-02-17 01:27:44 +03:00
23rd
bff3291631 Fixed clearing of text field in sections when sending files. 2021-02-12 13:05:50 +03:00
John Preston
e85394b520 Fix build with updated scheme. 2021-02-12 14:01:29 +04:00
John Preston
5cea5fc4e6 Fix window size in Tablet Mode on Windows 10. 2021-02-09 19:26:56 +04:00
John Preston
39742d22d9 Apply volume_by_admin flag in voice chats. 2021-02-09 19:26:56 +04:00
John Preston
d60a89f354 Update tgcalls. 2021-02-09 19:26:56 +04:00
23rd
90f90a4ca3 Fixed accepting of Enter key in box of voice message discarding. 2021-02-09 19:26:56 +04:00
23rd
776c099a25 Limited maximum number of selected messages for rescheduling to 20. 2021-02-09 19:26:56 +04:00
23rd
f2b434d82b Fixed text color in playback speed item. 2021-02-09 19:26:56 +04:00
Ilya Fedin
03e8d28456 Check for null manager type 2021-02-09 16:51:25 +04:00
Ilya Fedin
9b70f24e91 Adjust some tabs in gtk file dialog 2021-02-05 20:23:00 +04:00
Ilya Fedin
8fd1d16db6 Fix accept/reject lifetime in gtk file dialog 2021-02-05 20:23:00 +04:00
Ilya Fedin
36acf60f7e Add XDG Desktop Portal based file dialog implementation from Qt
This allows to use portal dialogs more flexibly (e.g. fallback mechanism)
This also allows to have any changes we want for portal dialogs without patchig Qt

No more need to override QT_QPA_PLATFORM to use portal dialogs
2021-02-05 20:23:00 +04:00
John Preston
ce1b94eb16 Send PDFs only as files.
Fixes #10294.
2021-02-05 13:18:11 +04:00
John Preston
e8affa85b0 Try to open localized changelog. 2021-02-04 20:42:32 +04:00
John Preston
d782ea63f8 Fix audio file forward inconsistencies. 2021-02-04 19:58:57 +04:00
John Preston
11b965e82e Skip image context actions for not-loaded photos. 2021-02-04 19:26:49 +04:00
John Preston
a2187a1d2b Fix pinned message reset on message being sent.
Fixes #10304
2021-02-04 19:12:31 +04:00
John Preston
7cd626c9fd Fix build, update lib_ui. 2021-02-04 18:46:04 +04:00
John Preston
37b8551760 Ignore 400: LOCATION_NOT_AVAILABLE in export.
Fix #6807.
2021-02-04 18:13:04 +04:00
John Preston
0cd8453b00 Support different invite link syntax. 2021-02-04 18:13:04 +04:00
23rd
0b4d0b83c2 Removed App::wnd from classes that have pointer to Window::Controller. 2021-02-04 18:13:04 +04:00
23rd
0783a682dc Removed App:wnd from Platform::MainWindow for macOS. 2021-02-04 18:13:04 +04:00
23rd
fb9a34a069 Removed App::wnd for opening about box. 2021-02-04 18:13:04 +04:00
23rd
b4af805521 Moved showLogoutConfirmation from MainWindow to Window::Controller. 2021-02-04 18:13:03 +04:00
23rd
1f80c297ec Removed App:wnd for opening settings.
Removed unused App::showSettings from facades.
2021-02-04 18:13:03 +04:00
23rd
019e691fbb Moved some session dependent methods to SessionController.
MainWindow::showAddContact(),
MainWindow::showNewGroup(),
MainWindow::showNewChannel().
2021-02-04 18:13:03 +04:00
23rd
683d78c64a Added missed passing of resize event to parent of OverlayWidget. 2021-02-04 18:13:03 +04:00
23rd
0aaa88cb79 Added animation to toggle silent broadcast button. 2021-02-04 18:13:03 +04:00
23rd
0cb8f2cc85 Added ability to toggle silent broadcast from sections.
Fixed #8655.
2021-02-04 18:13:03 +04:00
23rd
03a5619d61 Added ability to reschedule multiple messages.
Fixed #9851.
2021-02-04 18:13:03 +04:00
23rd
f1236edf5b Added ability to select message for reply with Ctrl+Up/Down in sections. 2021-02-04 18:13:03 +04:00
23rd
0b98cfbfec Added ability to attach file with shortcut in sections. 2021-02-04 18:13:03 +04:00
23rd
062c451c27 Refactored handle of last editable message on Up arrow in sections. 2021-02-04 18:13:03 +04:00
23rd
b13e5ddce9 Moved scroll keys from sections to compose controls. 2021-02-04 18:13:03 +04:00
23rd
4ad0837661 Fixed ability to attach file while editing message in sections. 2021-02-04 18:13:03 +04:00
23rd
4695ccfdb8 Fixed mouse hiding in OverlayWidget when menu of controls is displayed. 2021-02-04 18:13:03 +04:00
23rd
813470ff25 Replaced submenu for playback speed with slider. 2021-02-04 18:13:03 +04:00
23rd
2d50c61703 Added ability to set dividers to MediaSlider. 2021-02-04 18:13:03 +04:00
23rd
2dd99a535c United enum classes of arc directions into single enum class. 2021-02-04 18:13:03 +04:00
Ilya Fedin
57ca6e23b9 Port Qt-based title widget to lib_ui 2021-02-04 18:11:44 +04:00
Nicholas Guriev
153c949a88 Clean up DESKTOP_APP_DISABLE_WEBRTC_INTEGRATION 2021-02-04 17:39:33 +04:00
Ilya Fedin
76457c1e52 Restore QGuiApplication::setOverrideCursor usage
QWindow::setCursor doesn't work as expected...
2021-02-03 10:27:54 +04:00
Ilya Fedin
bcc333c2e1 Use QWindow::setCursor instead of QGuiApplication::setCursorOverride 2021-02-02 20:12:24 +04:00
Ilya Fedin
fe8bc30645 Use GCancellable to prevent crash in notificationShown 2021-02-02 20:11:09 +04:00
John Preston
f7b72bffe2 Fix build. 2021-02-02 17:39:51 +04:00
John Preston
5538c5eace Add 'Invite via Link' button to Add Members box. 2021-02-01 22:36:40 +04:00
John Preston
34ec1c371c Return invite-link-only box. 2021-02-01 19:10:59 +04:00
John Preston
cb2d77d386 Implement SingleChoiceBox using Ui::GenericBox. 2021-02-01 17:51:01 +04:00
John Preston
eb11185de7 Improve escaping of some data in HTML export. 2021-02-01 16:57:42 +04:00
John Preston
4e5c81dac2 Correctly handle 'min' group call participant updates. 2021-02-01 16:25:38 +04:00
John Preston
5feb381cb2 Allow showing images from cache in media viewer.
Fixes #10237.
2021-02-01 15:44:24 +04:00
John Preston
2a1096d83c Don't reset interface scale to auto on Settings open. 2021-02-01 12:32:08 +04:00
GitHub Action
d202a0cd06 Update User-Agent for DNS to Chrome 88.0.4324.96. 2021-02-01 11:29:58 +04:00
Ilya Fedin
3251b8bf6e Don't set geometry for media viewer only on Wayland 2021-01-31 12:39:13 +04:00
Ilya Fedin
f51055d606 Ensure the window is not out of available geometry on geometry restoring 2021-01-31 12:38:41 +04:00
Ilya Fedin
9c86755546 Take custom scale in account when saving window geometry 2021-01-31 12:38:41 +04:00
Ilya Fedin
0b5213a9cb Use SNAPCRAFT_PARALLEL_BUILD_COUNT instead of nproc in snap 2021-01-30 10:01:39 +04:00
John Preston
87895466dc Version 2.5.8: Fix dump_syms invocation. 2021-01-30 00:22:40 +04:00
John Preston
72f8d3f485 Version 2.5.8: Fix dump_syms invocation. 2021-01-30 00:05:49 +04:00
John Preston
5092d8fe63 Version 2.5.8: Fix invite link export if absent. 2021-01-29 23:49:20 +04:00
John Preston
01110a29ad Version 2.5.8: Fix PopupMenu min width. 2021-01-29 22:51:11 +04:00
John Preston
e8c3df2abb Version 2.5.8.
- Fix OpenAL device closing in calls and voice chats.
- Fix video chat rotation when calling from iOS.
- Fix scheduling messages without sound.
- Remove redundant Cancel button in ScheduleBox.
2021-01-29 22:10:13 +04:00
John Preston
db89de96a9 Fix loading of voice chats participants. 2021-01-29 22:07:32 +04:00
John Preston
1eacaec66b Fix build. 2021-01-29 21:07:09 +04:00
Ilya Fedin
10f58c2ac7 Add debug logging about media viewer screen 2021-01-29 20:58:35 +04:00
John Preston
e0680fc2a5 Fix voice chat menu for admins editing admins. 2021-01-29 20:56:56 +04:00
23rd
fba7bd7807 Fixed speaker display when participant is muted by me in group calls. 2021-01-29 18:24:52 +03:00
23rd
6c0553f4d6 Returned default icon color for songs without cover art.
Related commit: 9b9531d279.
2021-01-29 18:24:52 +03:00
John Preston
5d4e5ed527 Allow changing my own volume in voice chat. 2021-01-29 19:19:50 +04:00
John Preston
2559c5d6f4 Allow other admins to mute me in voice chats. 2021-01-29 19:19:50 +04:00
John Preston
8d85dd7c19 Fix scheduling messages without sound. 2021-01-29 18:05:13 +04:00
John Preston
8f0e23bb25 Improve editing messages with link previews.
Now preview state can be one of (allowed, cancelled, empty-in-edit).

In case of editing a message without preview we set the state to
empty-in-edit and it changes to allowed if the links in the message
are changed somehow.

That way we don't need to cancel the preview when editing a message
with a cancelled preview and at the same time adding a link to
a message that had no preview in the first place will add a preview.
2021-01-29 15:27:17 +04:00
John Preston
fc4ed2ff91 Fix repainting of non-first file album item. 2021-01-29 14:38:07 +04:00
John Preston
fcdc39c5f9 Add external_xxhash dependency to Telegram project. 2021-01-29 13:56:15 +04:00
John Preston
1ec6b4313d Remove redundant Cancel button in ScheduleBox. 2021-01-29 13:56:15 +04:00
Ilya Fedin
9be65f8812 Fix missing libraries warning in snap 2021-01-29 13:50:29 +04:00
Ilya Fedin
30468746ad Build Qt in snap to get newer version than in Ubuntu 2021-01-29 13:03:29 +04:00
John Preston
7947af665c Skip notifications from imported messages. 2021-01-28 22:19:58 +04:00
Ilya Fedin
160cd975ce Another attempt to implement shadows on Wayland
Works only with patched Qt
2021-01-28 22:19:34 +04:00
John Preston
c13d0e96ef Fix build. 2021-01-28 19:58:35 +04:00
John Preston
fbf4f912c6 Fix OpenAL device closing in calls and voice chats.
Also add 64 bit Windows info in crash platform string.
2021-01-28 19:31:54 +04:00
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
380 changed files with 13069 additions and 6036 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

@@ -67,6 +67,7 @@ PRIVATE
desktop-app::external_auto_updates
desktop-app::external_openssl
desktop-app::external_openal
desktop-app::external_xxhash
)
if (LINUX)
@@ -145,6 +146,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
@@ -179,6 +182,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
@@ -199,8 +206,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
@@ -265,6 +270,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
@@ -285,6 +291,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
@@ -821,10 +829,19 @@ PRIVATE
platform/linux/linux_gdk_helper.h
platform/linux/linux_gsd_media_keys.cpp
platform/linux/linux_gsd_media_keys.h
platform/linux/linux_libs.cpp
platform/linux/linux_libs.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_xdp_file_dialog.cpp
platform/linux/linux_xdp_file_dialog.h
platform/linux/linux_xlib_helper.cpp
platform/linux/linux_xlib_helper.h
platform/linux/file_utilities_linux.cpp
@@ -891,7 +908,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
@@ -961,6 +977,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
@@ -1114,6 +1132,10 @@ 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/linux_xdp_file_dialog.cpp
platform/linux/linux_xdp_file_dialog.h
platform/linux/notifications_manager_linux.cpp
)
@@ -1128,6 +1150,26 @@ if (LINUX AND DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
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.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 927 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

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";
@@ -824,6 +827,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_delete_contact" = "Delete";
"lng_profile_set_group_photo" = "Set Photo";
"lng_profile_add_participant" = "Add Members";
"lng_profile_add_via_link" = "Invite via Link";
"lng_profile_view_channel" = "View Channel";
"lng_profile_view_discussion" = "View discussion";
"lng_profile_join_channel" = "Join Channel";
@@ -932,6 +936,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 +980,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 +1170,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 +1228,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";
@@ -1535,6 +1583,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_copy_selected_items" = "Copy Selected as Text";
"lng_context_forward_selected" = "Forward Selected";
"lng_context_send_now_selected" = "Send selected now";
"lng_context_reschedule_selected" = "Reschedule Selected";
"lng_context_delete_selected" = "Delete Selected";
"lng_context_clear_selection" = "Clear Selection";
"lng_send_image_empty" = "Could not send an empty file: {name}";
@@ -1673,7 +1722,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mediaview_downloads" = "Downloads";
"lng_mediaview_video_loading" = "Loading - {percent}";
"lng_mediaview_playback_speed" = "Playback speed";
"lng_mediaview_playback_speed_normal" = "Normal";
"lng_mediaview_rotate_video" = "Rotate video";
"lng_theme_preview_title" = "Theme Preview";
@@ -1786,6 +1834,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 +1885,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 +1915,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 min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?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,10 +9,10 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.5.4.0" />
Version="2.5.9.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
<Description>Telegram Desktop official messenger</Description>
<Logo>Assets\logo\logo.png</Logo>
</Properties>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,5,4,0
PRODUCTVERSION 2,5,4,0
FILEVERSION 2,5,9,0
PRODUCTVERSION 2,5,9,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.4.0"
VALUE "FileVersion", "2.5.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.4.0"
VALUE "ProductVersion", "2.5.9.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,5,4,0
PRODUCTVERSION 2,5,4,0
FILEVERSION 2,5,9,0
PRODUCTVERSION 2,5,9,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.4.0"
VALUE "FileVersion", "2.5.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.5.4.0"
VALUE "ProductVersion", "2.5.9.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

@@ -128,9 +128,19 @@ void Authorizations::requestTerminate(
const auto send = [&](auto request) {
_api.request(
std::move(request)
).done(
std::move(done)
).fail(
).done([=, done = std::move(done)](const MTPBool &result) {
done(result);
if (mtpIsTrue(result)) {
if (hash) {
_list.erase(
ranges::remove(_list, *hash, &Entry::hash),
end(_list));
} else {
_list.clear();
}
_listChanges.fire({});
}
}).fail(
std::move(fail)
).send();
};

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);
@@ -2472,7 +2449,7 @@ void ApiWrap::saveDraftsToCloud() {
auto flags = MTPmessages_SaveDraft::Flags(0);
auto &textWithTags = cloudDraft->textWithTags;
if (cloudDraft->previewCancelled) {
if (cloudDraft->previewState != Data::PreviewState::Allowed) {
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
}
if (cloudDraft->msgId) {
@@ -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

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h"
#include "core/click_handler_types.h"
#include "core/update_checker.h"
#include "core/application.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -88,8 +89,10 @@ void AboutBox::resizeEvent(QResizeEvent *e) {
void AboutBox::showVersionHistory() {
if (cRealAlphaVersion()) {
auto url = qsl("https://tdesktop.com/");
if (Platform::IsWindows()) {
if (Platform::IsWindows32Bit()) {
url += qsl("win/%1.zip");
} else if (Platform::IsWindows64Bit()) {
url += qsl("win64/%1.zip");
} else if (Platform::IsOSXBuild()) {
url += qsl("osx/%1.zip");
} else if (Platform::IsMac()) {
@@ -107,7 +110,7 @@ void AboutBox::showVersionHistory() {
Ui::show(Box<InformBox>("The link to the current private alpha version of Telegram Desktop was copied to the clipboard."));
} else {
UrlClickHandler::Open(qsl("https://desktop.telegram.org/changelog"));
UrlClickHandler::Open(Core::App().changelogLink());
}
}
@@ -143,5 +146,8 @@ QString currentVersionText() {
} else if (AppBetaVersion) {
result += " beta";
}
if (Platform::IsWindows64Bit()) {
result += " x64";
}
return result;
}

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,55 @@ pollResultsShowMore: SettingsButton(defaultSettingsButton) {
ripple: defaultRippleAnimation;
}
inviteViaLinkButton: SettingsButton(defaultSettingsButton) {
textFg: lightButtonFg;
textFgOver: lightButtonFgOver;
textBg: windowBg;
textBgOver: windowBgOver;
font: font(14px semibold);
height: 20px;
padding: margins(74px, 8px, 8px, 9px);
ripple: defaultRippleAnimation;
}
inviteViaLinkIcon: icon {{ "info/edit/group_manage_links", lightButtonFg }};
inviteViaLinkIconPosition: point(23px, 2px);
peerListWithInviteViaLink: PeerList(peerListBox) {
padding: margins(
0px,
0px,
0px,
membersMarginBottom);
}
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
? (_thumbw ? st::historyFileSongPlay : st::historyFileInPlay)
: _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

@@ -1161,11 +1161,7 @@ void PeerListContent::enterEventHook(QEvent *e) {
void PeerListContent::leaveEventHook(QEvent *e) {
setMouseTracking(false);
if (_mouseSelection) {
setSelected(Selected());
_mouseSelection = false;
_lastMousePosition = std::nullopt;
}
mouseLeftGeometry();
}
void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
@@ -1440,7 +1436,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) {
@@ -1497,6 +1496,14 @@ void PeerListContent::clearSelection() {
setSelected(Selected());
}
void PeerListContent::mouseLeftGeometry() {
if (_mouseSelection) {
setSelected(Selected());
_mouseSelection = false;
_lastMousePosition = std::nullopt;
}
}
void PeerListContent::loadProfilePhotos() {
if (_visibleTop >= _visibleBottom) return;

View File

@@ -261,6 +261,7 @@ public:
virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) = 0;
virtual void peerListMouseLeftGeometry() = 0;
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
virtual void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) = 0;
@@ -302,7 +303,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 +376,9 @@ public:
_delegate = delegate;
prepare();
}
[[nodiscard]] not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
void setStyleOverrides(
const style::PeerList *listSt,
@@ -453,9 +457,6 @@ public:
virtual ~PeerListController() = default;
protected:
not_null<PeerListDelegate*> delegate() const {
return _delegate;
}
PeerListSearchController *searchController() const {
return _searchController.get();
}
@@ -541,6 +542,8 @@ public:
void setHideEmpty(bool hide);
void refreshRows();
void mouseLeftGeometry();
void setSearchMode(PeerListSearchMode mode);
void changeCheckState(
not_null<PeerListRow*> row,
@@ -804,6 +807,9 @@ public:
void peerListSetSearchMode(PeerListSearchMode mode) override {
_content->setSearchMode(mode);
}
void peerListMouseLeftGeometry() override {
_content->mouseLeftGeometry();
}
void peerListSortRows(
Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
_content->reorderRows([&](
@@ -837,7 +843,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 +857,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->showAddContact(); });
};
return Box<PeerListBox>(
std::make_unique<ContactsBoxController>(

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/add_participants_box.h"
#include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/edit_peer_type_box.h"
#include "boxes/confirm_box.h"
#include "lang/lang_keys.h"
#include "data/data_channel.h"
@@ -18,15 +19,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "history/history.h"
#include "dialogs/dialogs_indexed_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/padding_wrap.h"
#include "base/unixtime.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "window/window_session_controller.h"
#include "info/profile/info_profile_icon.h"
#include "apiwrap.h"
#include "facades.h" // Ui::showPeerHistory
#include "app.h"
#include "styles/style_boxes.h"
namespace {
@@ -68,6 +73,9 @@ AddParticipantsBoxController::AddParticipantsBoxController(
: ContactsBoxController(&peer->session())
, _peer(peer)
, _alreadyIn(std::move(alreadyIn)) {
if (needsInviteLinkButton()) {
setStyleOverrides(&st::peerListWithInviteViaLink);
}
subscribeToMigration();
}
@@ -166,6 +174,44 @@ void AddParticipantsBoxController::updateTitle() {
).arg(session().serverConfig().megagroupSizeMax);
delegate()->peerListSetTitle(tr::lng_profile_add_participant());
delegate()->peerListSetAdditionalTitle(rpl::single(additional));
addInviteLinkButton();
}
bool AddParticipantsBoxController::needsInviteLinkButton() {
if (!_peer) {
return false;
} else if (const auto channel = _peer->asChannel()) {
return channel->canHaveInviteLink();
}
return _peer->asChat()->canHaveInviteLink();
}
void AddParticipantsBoxController::addInviteLinkButton() {
if (!needsInviteLinkButton()) {
return;
}
auto button = object_ptr<Ui::PaddingWrap<Ui::SettingsButton>>(
nullptr,
object_ptr<Ui::SettingsButton>(
nullptr,
tr::lng_profile_add_via_link(),
st::inviteViaLinkButton),
style::margins(0, st::membersMarginTop, 0, 0));
object_ptr<Info::Profile::FloatingIcon>(
button->entity(),
st::inviteViaLinkIcon,
st::inviteViaLinkIconPosition);
button->entity()->setClickedCallback([=] {
Ui::show(Box<EditPeerTypeBox>(_peer), Ui::LayerOption::KeepOther);
});
button->entity()->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter);
}) | rpl::start_with_next([=] {
delegate()->peerListMouseLeftGeometry();
}, button->lifetime());
delegate()->peerListSetAboveWidget(std::move(button));
}
bool AddParticipantsBoxController::inviteSelectedUsers(
@@ -283,15 +329,21 @@ Main::Session &AddSpecialBoxController::session() const {
}
void AddSpecialBoxController::subscribeToMigration() {
const auto chat = _peer->asChat();
if (!chat) {
return;
}
SubscribeToMigration(
_peer,
chat,
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(channel); });
[=](not_null<ChannelData*> channel) { migrate(chat, channel); });
}
void AddSpecialBoxController::migrate(not_null<ChannelData*> channel) {
void AddSpecialBoxController::migrate(
not_null<ChatData*> chat,
not_null<ChannelData*> channel) {
_peer = channel;
_additional.migrate(channel);
_additional.migrate(chat, channel);
}
std::unique_ptr<PeerListRow> AddSpecialBoxController::createSearchRow(

View File

@@ -44,6 +44,7 @@ protected:
void prepareViewHook() override;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override;
virtual bool needsInviteLinkButton();
private:
static void Start(
@@ -52,6 +53,7 @@ private:
base::flat_set<not_null<UserData*>> &&alreadyIn,
bool justCreated);
void addInviteLinkButton();
bool inviteSelectedUsers(not_null<PeerListBox*> box) const;
void subscribeToMigration();
int alreadyInCount() const;
@@ -119,7 +121,7 @@ private:
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
void subscribeToMigration();
void migrate(not_null<ChannelData*> channel);
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
not_null<PeerData*> _peer;
MTP::Sender _api;

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"
@@ -208,10 +208,10 @@ EditAdminBox::EditAdminBox(
, _oldRank(rank) {
}
MTPChatAdminRights EditAdminBox::Defaults(not_null<PeerData*> peer) {
const auto defaultRights = peer->isChat()
? ChatData::DefaultAdminRights()
: peer->isMegagroup()
MTPChatAdminRights EditAdminBox::defaultRights() const {
const auto flags = peer()->isChat()
? peer()->asChat()->defaultAdminRights(user())
: peer()->isMegagroup()
? (Flag::f_change_info
| Flag::f_delete_messages
| Flag::f_ban_users
@@ -223,7 +223,7 @@ MTPChatAdminRights EditAdminBox::Defaults(not_null<PeerData*> peer) {
| Flag::f_edit_messages
| Flag::f_delete_messages
| Flag::f_invite_users);
return MTP_chatAdminRights(MTP_flags(defaultRights));
return MTP_chatAdminRights(MTP_flags(flags));
}
void EditAdminBox::prepare() {
@@ -242,7 +242,7 @@ void EditAdminBox::prepare() {
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
const auto prepareRights = hadRights ? _oldRights : Defaults(peer());
const auto prepareRights = hadRights ? _oldRights : defaultRights();
const auto disabledByDefaults = (channel && !channel->isMegagroup())
? MTPDchatAdminRights::Flags(0)
: DisabledByDefaultRestrictions(peer());
@@ -264,12 +264,12 @@ void EditAdminBox::prepare() {
result.emplace(
disabledByDefaults,
tr::lng_rights_permission_for_all(tr::now));
if (const auto channel = peer()->asChannel()) {
if (amCreator() && user()->isSelf()) {
result.emplace(
~Flag::f_anonymous,
tr::lng_rights_permission_cant_edit(tr::now));
} else if (!channel->amCreator()) {
if (amCreator() && user()->isSelf()) {
result.emplace(
~Flag::f_anonymous,
tr::lng_rights_permission_cant_edit(tr::now));
} else if (const auto channel = peer()->asChannel()) {
if (!channel->amCreator()) {
result.emplace(
~channel->adminRights(),
tr::lng_rights_permission_cant_edit(tr::now));
@@ -611,9 +611,9 @@ void EditRestrictedBox::prepare() {
const auto defaultRestrictions = chat
? chat->defaultRestrictions()
: channel->defaultRestrictions();
const auto prepareRights = (_oldRights.c_chatBannedRights().vflags().v
const auto prepareRights = _oldRights.c_chatBannedRights().vflags().v
? _oldRights
: Defaults(peer()));
: defaultRights();
const auto prepareFlags = FixDependentRestrictions(
prepareRights.c_chatBannedRights().vflags().v
| defaultRestrictions
@@ -680,7 +680,7 @@ void EditRestrictedBox::prepare() {
}
}
MTPChatBannedRights EditRestrictedBox::Defaults(not_null<PeerData*> peer) {
MTPChatBannedRights EditRestrictedBox::defaultRights() const {
return MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
}
@@ -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 {
@@ -89,7 +89,7 @@ private:
using Flag = MTPDchatAdminRights::Flag;
using Flags = MTPDchatAdminRights::Flags;
static MTPChatAdminRights Defaults(not_null<PeerData*> peer);
[[nodiscard]] MTPChatAdminRights defaultRights() const;
not_null<Ui::InputField*> addRankInput();
void transferOwnership();
@@ -144,7 +144,7 @@ private:
using Flag = MTPDchatBannedRights::Flag;
using Flags = MTPDchatBannedRights::Flags;
static MTPChatBannedRights Defaults(not_null<PeerData*> peer);
[[nodiscard]] MTPChatBannedRights defaultRights() const;
bool canSave() const {
return !!_saveCallback;
@@ -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) {
@@ -223,7 +224,7 @@ Fn<void(
const MTPDchatAdminRights &data) {
return data.vflags().v;
});
if (flags == ChatData::DefaultAdminRights() && rank.isEmpty()) {
if (flags == chat->defaultAdminRights(user) && rank.isEmpty()) {
saveChatAdmin(true);
} else if (!flags) {
saveChatAdmin(false);
@@ -358,7 +359,9 @@ bool ParticipantsAdditionalData::canRemoveUser(
if (canRestrictUser(user)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return !user->isSelf() && chat->invitedByMe.contains(user);
return !user->isSelf()
&& chat->invitedByMe.contains(user)
&& (chat->amCreator() || !_admins.contains(user));
}
return false;
}
@@ -369,7 +372,7 @@ auto ParticipantsAdditionalData::adminRights(
if (const auto chat = _peer->asChat()) {
return _admins.contains(user)
? std::make_optional(MTPChatAdminRights(MTP_chatAdminRights(
MTP_flags(ChatData::DefaultAdminRights()))))
MTP_flags(chat->defaultAdminRights(user)))))
: std::nullopt;
}
const auto i = _adminRights.find(user);
@@ -670,14 +673,16 @@ UserData *ParticipantsAdditionalData::applyBanned(
return user;
}
void ParticipantsAdditionalData::migrate(not_null<ChannelData*> channel) {
void ParticipantsAdditionalData::migrate(
not_null<ChatData*> chat,
not_null<ChannelData*> channel) {
_peer = channel;
fillFromChannel(channel);
for (const auto user : _admins) {
_adminRights.emplace(
user,
MTP_chatAdminRights(MTP_flags(ChatData::DefaultAdminRights())));
MTP_chatAdminRights(MTP_flags(chat->defaultAdminRights(user))));
if (channel->amCreator()) {
_adminCanEdit.emplace(user);
}
@@ -1888,15 +1893,21 @@ void ParticipantsBoxController::refreshCustomStatus(
}
void ParticipantsBoxController::subscribeToMigration() {
const auto chat = _peer->asChat();
if (!chat) {
return;
}
SubscribeToMigration(
_peer,
chat,
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(channel); });
[=](not_null<ChannelData*> channel) { migrate(chat, channel); });
}
void ParticipantsBoxController::migrate(not_null<ChannelData*> channel) {
void ParticipantsBoxController::migrate(
not_null<ChatData*> chat,
not_null<ChannelData*> channel) {
_peer = channel;
_additional.migrate(channel);
_additional.migrate(chat, channel);
subscribeToCreatorChange(channel);
}

View File

@@ -101,7 +101,7 @@ public:
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
void migrate(not_null<ChannelData*> channel);
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
private:
UserData *applyCreator(const MTPDchannelParticipantCreator &data);
@@ -242,7 +242,7 @@ private:
void recomputeTypeFor(not_null<UserData*> user);
void subscribeToMigration();
void migrate(not_null<ChannelData*> channel);
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
void subscribeToCreatorChange(not_null<ChannelData*> channel);
void fullListRefresh();

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 {
@@ -768,6 +815,7 @@ void Controller::fillInviteLinkButton() {
void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto channel = _peer->asChannel();
if (!channel) return;
@@ -845,13 +893,6 @@ void Controller::fillManageSection() {
? channel->canEditUsername()
: chat->canEditUsername();
}();
const auto canEditInviteLink = [&] {
return isChannel
? (channel->amCreator()
|| (channel->adminRights() & ChatAdminRight::f_invite_users))
: (chat->amCreator()
|| (chat->adminRights() & ChatAdminRight::f_invite_users));
}();
const auto canEditSignatures = [&] {
return isChannel
? (channel->canEditSignatures() && !channel->isMegagroup())
@@ -868,6 +909,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,7 +959,7 @@ void Controller::fillManageSection() {
if (canEditUsername) {
fillPrivacyTypeButton();
} else if (canEditInviteLink) {
} else if (canEditInviteLinks) {
fillInviteLinkButton();
}
if (canViewOrEditLinkedChat) {
@@ -927,7 +973,7 @@ void Controller::fillManageSection() {
}
if (canEditPreHistoryHidden
|| canEditSignatures
|| canEditInviteLink
|| canEditInviteLinks
|| canViewOrEditLinkedChat
|| canEditUsername) {
AddSkip(
@@ -949,6 +995,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>
@@ -65,17 +72,13 @@ public:
void setFocusUsername();
rpl::producer<QString> getTitle() {
return _isInviteLink
? tr::lng_profile_invite_link_section()
return !_privacySavedValue
? tr::lng_create_invite_link_title()
: _isGroup
? tr::lng_manage_peer_group_type()
: tr::lng_manage_peer_channel_type();
}
bool isInviteLink() {
return _isInviteLink;
}
bool isAllowSave() {
return _isAllowSave;
}
@@ -97,19 +100,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,16 +120,9 @@ 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);
std::optional<Privacy> savedValue);
void addRoundButton(
not_null<Ui::VerticalLayout*> container,
Privacy value,
@@ -142,13 +133,14 @@ private:
QString inviteLinkText();
not_null<PeerData*> _peer;
bool _linkOnly = false;
MTP::Sender _api;
std::optional<Privacy> _privacySavedValue;
std::optional<QString> _usernameSavedValue;
bool _useLocationPhrases = false;
bool _isGroup = false;
bool _isInviteLink = false;
bool _isAllowSave = false;
base::unique_qptr<Ui::VerticalLayout> _wrap;
@@ -168,13 +160,12 @@ Controller::Controller(
std::optional<Privacy> privacySavedValue,
std::optional<QString> usernameSavedValue)
: _peer(peer)
, _linkOnly(!privacySavedValue.has_value())
, _api(&_peer->session().mtp())
, _privacySavedValue(privacySavedValue)
, _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,22 +175,45 @@ 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));
if (!_linkOnly) {
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
}
//
_wrap->add(createInviteLinkCreate());
_wrap->add(createInviteLinkEdit());
_wrap->add(createUsernameEdit());
_wrap->add(createInviteLinkBlock());
if (!_linkOnly) {
_wrap->add(createUsernameEdit());
}
if (_controls.privacy->value() == Privacy::NoUsername) {
checkUsernameAvailability();
//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 (_linkOnly) {
_controls.inviteLinkWrap->show(anim::type::instant);
} else {
if (_controls.privacy->value() == Privacy::NoUsername) {
checkUsernameAvailability();
}
const auto forShowing = _privacySavedValue.value_or(Privacy::NoUsername);
_controls.inviteLinkWrap->toggle(
(forShowing != Privacy::HasUsername),
anim::type::instant);
_controls.usernameWrap->toggle(
(forShowing == Privacy::HasUsername),
anim::type::instant);
}
}
@@ -237,7 +251,7 @@ void Controller::fillPrivaciesButtons(
}
Unexpected("Peer type in Controller::createPrivaciesEdit.");
}();
if (!canEditUsername) {
if (!canEditUsername || _linkOnly) {
return;
}
@@ -312,21 +326,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 +364,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 +380,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 +395,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,181 +550,53 @@ 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));
using namespace Settings;
if (_privacySavedValue) {
AddSkip(container);
AddSubsectionTitle(container, tr::lng_create_invite_link_title());
}
// tr::lng_create_permanent_link_title()); // #TODO links
AddPermanentLinkBlock(container, _peer);
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
AddSkip(container);
AddDividerText(
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;
});
((_peer->isMegagroup() || _peer->asChat())
? tr::lng_group_invite_about_permanent_group()
: tr::lng_group_invite_about_permanent_channel()));
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(); });
observeInviteLink();
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());
if (_peer->wasFullUpdated()) {
const auto link = _peer->isChat()
? _peer->asChat()->inviteLink()
: _peer->asChannel()->inviteLink();
if (link.isEmpty()) {
// #TODO links remove this auto-export link.
_peer->session().api().inviteLinks().revokePermanent(_peer);
}
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,
@@ -726,6 +613,12 @@ EditPeerTypeBox::EditPeerTypeBox(
, _usernameError(usernameError) {
}
EditPeerTypeBox::EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer)
: EditPeerTypeBox(nullptr, peer, {}, {}, {}, {}, {}) {
}
void EditPeerTypeBox::setInnerFocus() {
_focusRequests.fire({});
}
@@ -733,11 +626,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 +649,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 +661,14 @@ 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(tr::lng_cancel(), [=] { closeBox(); });
} else {
addButton(tr::lng_close(), [=] { closeBox(); });
}
addButton(
controller->isInviteLink() ? tr::lng_close() : tr::lng_cancel(),
[=] { closeBox(); });
setDimensionsToContent(st::boxWideWidth, content);
setDimensionsToContent(st::boxWideWidth, content.data());
setInnerWidget(std::move(content));
}

View File

@@ -32,9 +32,6 @@ 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,
@@ -44,6 +41,11 @@ public:
std::optional<QString> usernameSaved,
std::optional<rpl::producer<QString>> usernameError = {});
// For invite link only.
EditPeerTypeBox(
QWidget*,
not_null<PeerData*> peer);
protected:
void prepare() override;
void setInnerFocus() override;

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

@@ -262,13 +262,13 @@ void SessionsContent::terminateOne(uint64 hash) {
const auto weak = Ui::MakeWeak(this);
auto callback = [=] {
auto done = crl::guard(weak, [=](const MTPBool &result) {
if (mtpIsFalse(result)) {
return;
}
_inner->terminatingOne(hash, false);
const auto getHash = [](const Entry &entry) {
return entry.hash;
};
const auto removeByHash = [&](std::vector<Entry> &list) {
list.erase(
ranges::remove(list, hash, getHash),
ranges::remove(list, hash, &Entry::hash),
end(list));
};
removeByHash(_data.incomplete);

View File

@@ -16,56 +16,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
SingleChoiceBox::SingleChoiceBox(
QWidget*,
rpl::producer<QString> title,
const std::vector<QString> &optionTexts,
int initialSelection,
Fn<void(int)> callback,
const style::Checkbox *st,
const style::Radio *radioSt)
: _title(std::move(title))
, _optionTexts(optionTexts)
, _initialSelection(initialSelection)
, _callback(callback)
, _st(st ? *st : st::defaultBoxCheckbox)
, _radioSt(radioSt ? *radioSt : st::defaultRadio) {
}
void SingleChoiceBox(
not_null<Ui::GenericBox*> box,
SingleChoiceBoxArgs &&args) {
box->setTitle(std::move(args.title));
void SingleChoiceBox::prepare() {
setTitle(std::move(_title));
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
addButton(tr::lng_box_ok(), [=] { closeBox(); });
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
args.initialSelection);
const auto group = std::make_shared<Ui::RadiobuttonGroup>(_initialSelection);
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
content->add(object_ptr<Ui::FixedHeightWidget>(
content,
const auto layout = box->verticalLayout();
layout->add(object_ptr<Ui::FixedHeightWidget>(
layout,
st::boxOptionListPadding.top() + st::autolockButton.margin.top()));
auto &&ints = ranges::view::ints(0, ranges::unreachable);
for (const auto &[i, text] : ranges::view::zip(ints, _optionTexts)) {
content->add(
for (const auto &[i, text] : ranges::view::zip(ints, args.options)) {
layout->add(
object_ptr<Ui::Radiobutton>(
content,
layout,
group,
i,
text,
_st,
_radioSt),
args.st ? *args.st : st::defaultBoxCheckbox,
args.radioSt ? *args.radioSt : st::defaultRadio),
QMargins(
st::boxPadding.left() + st::boxOptionListPadding.left(),
0,
st::boxPadding.right(),
st::boxOptionListSkip));
}
const auto callback = args.callback;
group->setChangedCallback([=](int value) {
const auto weak = Ui::MakeWeak(this);
_callback(value);
const auto weak = Ui::MakeWeak(box);
callback(value);
if (weak) {
closeBox();
box->closeBox();
}
});
setDimensionsToContent(st::boxWidth, content);
}

View File

@@ -7,38 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/abstract_box.h"
#include <vector>
namespace Ui {
class Radiobutton;
} // namespace Ui
#include "ui/layers/generic_box.h"
#include "base/required.h"
namespace style {
struct Checkbox;
struct Radio;
} // namespace style
class SingleChoiceBox : public Ui::BoxContent {
public:
SingleChoiceBox(
QWidget*,
rpl::producer<QString> title,
const std::vector<QString> &optionTexts,
int initialSelection,
Fn<void(int)> callback,
const style::Checkbox *st = nullptr,
const style::Radio *radioSt = nullptr);
protected:
void prepare() override;
private:
rpl::producer<QString> _title;
std::vector<QString> _optionTexts;
int _initialSelection = 0;
Fn<void(int)> _callback;
const style::Checkbox &_st;
const style::Radio &_radioSt;
struct SingleChoiceBoxArgs {
template <typename T>
using required = base::required<T>;
required<rpl::producer<QString>> title;
const std::vector<QString> &options;
int initialSelection = 0;
Fn<void(int)> callback;
const style::Checkbox *st = nullptr;
const style::Radio *radioSt = nullptr;
};
void SingleChoiceBox(
not_null<Ui::GenericBox*> box,
SingleChoiceBoxArgs &&args);

View File

@@ -473,11 +473,9 @@ void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
int index = stickerFromGlobalPos(e->globalPos());
if (index >= 0 && index < _pack.size() && index != _previewShown) {
_previewShown = index;
if (const auto w = App::wnd()) {
w->showMediaPreview(
Data::FileOriginStickerSet(_setId, _setAccess),
_pack[_previewShown]);
}
_controller->widget()->showMediaPreview(
Data::FileOriginStickerSet(_setId, _setAccess),
_pack[_previewShown]);
}
}
}
@@ -536,11 +534,9 @@ void StickerSetBox::Inner::showPreview() {
int index = stickerFromGlobalPos(QCursor::pos());
if (index >= 0 && index < _pack.size()) {
_previewShown = index;
if (const auto w = App::wnd()) {
w->showMediaPreview(
Data::FileOriginStickerSet(_setId, _setAccess),
_pack[_previewShown]);
}
_controller->widget()->showMediaPreview(
Data::FileOriginStickerSet(_setId, _setAccess),
_pack[_previewShown]);
}
}

View File

@@ -22,20 +22,6 @@ CallSignalBars {
inactiveOpacity: double;
}
callRadius: 6px;
callShadow: Shadow {
left: icon {{ "calls/call_shadow_left", windowShadowFg }};
topLeft: icon {{ "calls/call_shadow_top_left", windowShadowFg }};
top: icon {{ "calls/call_shadow_top", windowShadowFg }};
topRight: icon {{ "calls/call_shadow_top_left-flip_horizontal", windowShadowFg }};
right: icon {{ "calls/call_shadow_left-flip_horizontal", windowShadowFg }};
bottomRight: icon {{ "calls/call_shadow_top_left-flip_vertical-flip_horizontal", windowShadowFg }};
bottom: icon {{ "calls/call_shadow_top-flip_vertical", windowShadowFg }};
bottomLeft: icon {{ "calls/call_shadow_top_left-flip_vertical", windowShadowFg }};
extend: margins(9px, 8px, 9px, 10px);
fallback: windowShadowFgFallback;
}
callWidthMin: 300px;
callHeightMin: 440px;
callWidth: 720px;
@@ -778,6 +764,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

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