Compare commits
256 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3660f1ed8 | ||
|
|
70570e0987 | ||
|
|
baccec623d | ||
|
|
093d89db83 | ||
|
|
48fea47d16 | ||
|
|
8500bf6073 | ||
|
|
69b41fadef | ||
|
|
d44f076f0b | ||
|
|
3637fec397 | ||
|
|
e109da037e | ||
|
|
3a659b4b54 | ||
|
|
7e4dff25e9 | ||
|
|
da74fe4248 | ||
|
|
294f849775 | ||
|
|
88951e9e5c | ||
|
|
153b91248d | ||
|
|
7977331d8b | ||
|
|
8ec60e0321 | ||
|
|
dcebefe2bb | ||
|
|
93a88b54ad | ||
|
|
07f94cc184 | ||
|
|
9a0edbd0c5 | ||
|
|
c935f1bb16 | ||
|
|
3cd05a34d9 | ||
|
|
223681d2da | ||
|
|
c3c1759f3c | ||
|
|
bff3291631 | ||
|
|
e85394b520 | ||
|
|
5cea5fc4e6 | ||
|
|
39742d22d9 | ||
|
|
d60a89f354 | ||
|
|
90f90a4ca3 | ||
|
|
776c099a25 | ||
|
|
f2b434d82b | ||
|
|
03e8d28456 | ||
|
|
9b70f24e91 | ||
|
|
8fd1d16db6 | ||
|
|
36acf60f7e | ||
|
|
ce1b94eb16 | ||
|
|
e8affa85b0 | ||
|
|
d782ea63f8 | ||
|
|
11b965e82e | ||
|
|
a2187a1d2b | ||
|
|
7cd626c9fd | ||
|
|
37b8551760 | ||
|
|
0cd8453b00 | ||
|
|
0b4d0b83c2 | ||
|
|
0783a682dc | ||
|
|
fb9a34a069 | ||
|
|
b4af805521 | ||
|
|
1f80c297ec | ||
|
|
019e691fbb | ||
|
|
683d78c64a | ||
|
|
0aaa88cb79 | ||
|
|
0cb8f2cc85 | ||
|
|
03a5619d61 | ||
|
|
f1236edf5b | ||
|
|
0b98cfbfec | ||
|
|
062c451c27 | ||
|
|
b13e5ddce9 | ||
|
|
4ad0837661 | ||
|
|
4695ccfdb8 | ||
|
|
813470ff25 | ||
|
|
2d50c61703 | ||
|
|
2dd99a535c | ||
|
|
57ca6e23b9 | ||
|
|
153c949a88 | ||
|
|
76457c1e52 | ||
|
|
bcc333c2e1 | ||
|
|
fe8bc30645 | ||
|
|
f7b72bffe2 | ||
|
|
5538c5eace | ||
|
|
34ec1c371c | ||
|
|
cb2d77d386 | ||
|
|
eb11185de7 | ||
|
|
4e5c81dac2 | ||
|
|
5feb381cb2 | ||
|
|
2a1096d83c | ||
|
|
d202a0cd06 | ||
|
|
3251b8bf6e | ||
|
|
f51055d606 | ||
|
|
9c86755546 | ||
|
|
0b5213a9cb | ||
|
|
87895466dc | ||
|
|
72f8d3f485 | ||
|
|
5092d8fe63 | ||
|
|
01110a29ad | ||
|
|
e8c3df2abb | ||
|
|
db89de96a9 | ||
|
|
1eacaec66b | ||
|
|
10f58c2ac7 | ||
|
|
e0680fc2a5 | ||
|
|
fba7bd7807 | ||
|
|
6c0553f4d6 | ||
|
|
5d4e5ed527 | ||
|
|
2559c5d6f4 | ||
|
|
8d85dd7c19 | ||
|
|
8f0e23bb25 | ||
|
|
fc4ed2ff91 | ||
|
|
fcdc39c5f9 | ||
|
|
1ec6b4313d | ||
|
|
9be65f8812 | ||
|
|
30468746ad | ||
|
|
7947af665c | ||
|
|
160cd975ce | ||
|
|
c13d0e96ef | ||
|
|
fbf4f912c6 | ||
|
|
9a0023cc99 | ||
|
|
0aea9bc46f | ||
|
|
d2662ba1fd | ||
|
|
0f17a3b300 | ||
|
|
7c031a4fb6 | ||
|
|
834ee4eae7 | ||
|
|
9b59e74d66 | ||
|
|
ec8ddb047d | ||
|
|
037506c0b7 | ||
|
|
85b3672bc8 | ||
|
|
bf61f624c5 | ||
|
|
1baa833e8f | ||
|
|
bad2d8afd9 | ||
|
|
6d5bf53dd1 | ||
|
|
3001ad4b89 | ||
|
|
5a88b4f0b9 | ||
|
|
71ee981371 | ||
|
|
e9864bcf5b | ||
|
|
dd401a063b | ||
|
|
61f6851486 | ||
|
|
7da224d725 | ||
|
|
ce5c19dfe9 | ||
|
|
52000566cf | ||
|
|
fa8dd61b02 | ||
|
|
0d0a79b0b5 | ||
|
|
b22c65a8db | ||
|
|
416489a84f | ||
|
|
7424e6afcc | ||
|
|
5d1f55e29d | ||
|
|
130b8bc83c | ||
|
|
ef1a4e4ce3 | ||
|
|
9fed46fb6e | ||
|
|
50f87cce84 | ||
|
|
833ffe1784 | ||
|
|
1d3e76e1fe | ||
|
|
1cbb217210 | ||
|
|
70cdc05544 | ||
|
|
173564bcd5 | ||
|
|
e12689c8c1 | ||
|
|
e1f5e10764 | ||
|
|
250add3a96 | ||
|
|
827c950468 | ||
|
|
36ad24bfcd | ||
|
|
7410c1fc73 | ||
|
|
b2c84d675c | ||
|
|
ff9bf23461 | ||
|
|
417428b21d | ||
|
|
58733ba6ea | ||
|
|
1774b21e88 | ||
|
|
19455d44db | ||
|
|
34f7391ec9 | ||
|
|
274779c1c8 | ||
|
|
50c07bfc98 | ||
|
|
819cd4a099 | ||
|
|
144bad6c74 | ||
|
|
97fb310f54 | ||
|
|
1cce383d15 | ||
|
|
01ecf0ca93 | ||
|
|
40e90af76d | ||
|
|
7fa342b487 | ||
|
|
3862b3b90e | ||
|
|
5e10d97abe | ||
|
|
542abb26b9 | ||
|
|
7132ab5bf4 | ||
|
|
c7b1a37722 | ||
|
|
be1afb4781 | ||
|
|
8c7030378a | ||
|
|
7e89ed48c2 | ||
|
|
754dedc40e | ||
|
|
e5320b4b4e | ||
|
|
02ad5f2772 | ||
|
|
b58a977029 | ||
|
|
40fda9503f | ||
|
|
f63f0a7668 | ||
|
|
b396244606 | ||
|
|
b562a4a479 | ||
|
|
82d78e7c45 | ||
|
|
df0bca077e | ||
|
|
efde011f1c | ||
|
|
9b9531d279 | ||
|
|
d4bbbdb65c | ||
|
|
c90258664d | ||
|
|
dd01ece14a | ||
|
|
4895e5e110 | ||
|
|
c21125f9f2 | ||
|
|
ae74f7b3da | ||
|
|
8ed56bb4e4 | ||
|
|
3793f7c3c9 | ||
|
|
0d1b778612 | ||
|
|
b919a0627a | ||
|
|
6374d4eeda | ||
|
|
3967052375 | ||
|
|
89ccc95023 | ||
|
|
24f2ca7443 | ||
|
|
f90e13f8b1 | ||
|
|
606f5377d5 | ||
|
|
c698327b24 | ||
|
|
655731741c | ||
|
|
d5cdb5582b | ||
|
|
5cb081ca9a | ||
|
|
f1e0b36f61 | ||
|
|
ea9813825d | ||
|
|
36b6f70613 | ||
|
|
5e60b87cf9 | ||
|
|
ada22ee6cc | ||
|
|
bb016e1489 | ||
|
|
b115ea74d0 | ||
|
|
1008774aef | ||
|
|
73018ff958 | ||
|
|
e799fdaa3d | ||
|
|
7656a546b0 | ||
|
|
57f9ae4b2a | ||
|
|
cbdd86d398 | ||
|
|
2fe2105a5f | ||
|
|
a986d7a3d6 | ||
|
|
690c5df87c | ||
|
|
1e2759840d | ||
|
|
bad888496c | ||
|
|
4348ddf938 | ||
|
|
894d6028bd | ||
|
|
e8edbb16ae | ||
|
|
a0a71687e7 | ||
|
|
d042963a47 | ||
|
|
64b12bde55 | ||
|
|
49736cd879 | ||
|
|
e55581e0c9 | ||
|
|
574d915c23 | ||
|
|
2616659116 | ||
|
|
3d1f21bd05 | ||
|
|
dc631ef631 | ||
|
|
5277080115 | ||
|
|
1ccfcc824c | ||
|
|
97e8c0956f | ||
|
|
03a7131a1a | ||
|
|
2d906bddb2 | ||
|
|
dd7598a701 | ||
|
|
b6f17e1cea | ||
|
|
eb42a77eb7 | ||
|
|
ad761011d6 | ||
|
|
3fadf2ee54 | ||
|
|
15254599e2 | ||
|
|
1607752cf9 | ||
|
|
cf0cde6e83 | ||
|
|
8fffe7d128 | ||
|
|
a483eb98a1 | ||
|
|
838a3b23c7 | ||
|
|
a030911ad5 | ||
|
|
8ae1b10b91 | ||
|
|
adc8d6a6d1 |
21
.github/lock.yml
vendored
Normal 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
@@ -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.
|
||||
@@ -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()
|
||||
|
||||
|
Before Width: | Height: | Size: 100 B |
|
Before Width: | Height: | Size: 125 B |
|
Before Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 103 B |
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 559 B |
|
Before Width: | Height: | Size: 927 B |
BIN
Telegram/Resources/icons/calls/volume/speaker.png
Normal file
|
After Width: | Height: | Size: 556 B |
BIN
Telegram/Resources/icons/calls/volume/speaker@2x.png
Normal file
|
After Width: | Height: | Size: 998 B |
BIN
Telegram/Resources/icons/calls/volume/speaker@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/calls/volume/speaker_small.png
Normal file
|
After Width: | Height: | Size: 310 B |
BIN
Telegram/Resources/icons/calls/volume/speaker_small@2x.png
Normal file
|
After Width: | Height: | Size: 521 B |
BIN
Telegram/Resources/icons/calls/volume/speaker_small@3x.png
Normal file
|
After Width: | Height: | Size: 734 B |
BIN
Telegram/Resources/icons/info/edit/dotsmini.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
Telegram/Resources/icons/info/edit/dotsmini@2x.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
Telegram/Resources/icons/info/edit/dotsmini@3x.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_actions.png
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_actions@2x.png
Normal file
|
After Width: | Height: | Size: 780 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_actions@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins@2x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_admins@3x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_links.png
Normal file
|
After Width: | Height: | Size: 933 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_links@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_links@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_members.png
Normal file
|
After Width: | Height: | Size: 620 B |
BIN
Telegram/Resources/icons/info/edit/group_manage_members@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_members@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/info/edit/group_manage_permissions.png
Normal file
|
After Width: | Height: | Size: 980 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Telegram/Resources/icons/info/edit/links_copy.png
Normal file
|
After Width: | Height: | Size: 397 B |
BIN
Telegram/Resources/icons/info/edit/links_copy@2x.png
Normal file
|
After Width: | Height: | Size: 682 B |
BIN
Telegram/Resources/icons/info/edit/links_copy@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/info/edit/links_link.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
Telegram/Resources/icons/info/edit/links_link@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/info/edit/links_link@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/info/edit/links_share.png
Normal file
|
After Width: | Height: | Size: 464 B |
BIN
Telegram/Resources/icons/info/edit/links_share@2x.png
Normal file
|
After Width: | Height: | Size: 859 B |
BIN
Telegram/Resources/icons/info/edit/links_share@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@2x.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@3x.png
Normal file
|
After Width: | Height: | Size: 752 B |
|
Before Width: | Height: | Size: 572 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
631
Telegram/SourceFiles/api/api_invite_links.cpp
Normal 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
|
||||
187
Telegram/SourceFiles/api/api_invite_links.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -96,6 +96,8 @@ private:
|
||||
void createEditMediaButton();
|
||||
bool setPreparedList(Ui::PreparedList &&list);
|
||||
|
||||
bool isThumbedLayout() const;
|
||||
|
||||
inline QString getNewMediaPath() {
|
||||
return _preparedList.files.empty()
|
||||
? QString()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
591
Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
32
Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h
Normal 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);
|
||||
776
Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
Normal 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(); });
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||