Compare commits
289 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63919422e0 | ||
|
|
d6e6c51639 | ||
|
|
95a24d6aa1 | ||
|
|
fa28b0b405 | ||
|
|
42fcf4ceb2 | ||
|
|
3743dd0161 | ||
|
|
dab859ea29 | ||
|
|
a92394a81f | ||
|
|
7a57174ab1 | ||
|
|
52bacb3cde | ||
|
|
8ad9770118 | ||
|
|
aef45b3a1d | ||
|
|
87af865604 | ||
|
|
4efeaacf5c | ||
|
|
e73928f6a3 | ||
|
|
589ddb7b22 | ||
|
|
6d73b11d54 | ||
|
|
81ff4d7497 | ||
|
|
e4ac810773 | ||
|
|
7e9302fce8 | ||
|
|
bd2fe2f68e | ||
|
|
e73c049899 | ||
|
|
dab3bce8ba | ||
|
|
eae6e4fc60 | ||
|
|
79a8fced80 | ||
|
|
3bd9ccd45d | ||
|
|
1bfa97d4fd | ||
|
|
3b4b5d3576 | ||
|
|
98cb1478c7 | ||
|
|
099326f211 | ||
|
|
8b4a95826c | ||
|
|
27a9b61f72 | ||
|
|
bad193ec29 | ||
|
|
60444b1fee | ||
|
|
65d96c0364 | ||
|
|
28f85eb710 | ||
|
|
63485dbf7f | ||
|
|
d58b9dc954 | ||
|
|
7b8b50ecd5 | ||
|
|
5cbfdad2e8 | ||
|
|
51addb7320 | ||
|
|
e652b4b65e | ||
|
|
510e1e9cdf | ||
|
|
057e8ce391 | ||
|
|
1d655fb5b5 | ||
|
|
5761e7559a | ||
|
|
c55dc00180 | ||
|
|
27dc91e51a | ||
|
|
6ef67caa73 | ||
|
|
b36fce31d5 | ||
|
|
aaafa8b3f6 | ||
|
|
b4950fe943 | ||
|
|
7ad6699bff | ||
|
|
05efc925f8 | ||
|
|
06c9cac2e4 | ||
|
|
4b33d7cb2f | ||
|
|
9c0bf32b84 | ||
|
|
2a504dd90d | ||
|
|
97881e7754 | ||
|
|
e42f08f08d | ||
|
|
42015d6a72 | ||
|
|
094010a91d | ||
|
|
f71a2149f3 | ||
|
|
1297860652 | ||
|
|
c528ea24ea | ||
|
|
fa8792927e | ||
|
|
aaae5b0553 | ||
|
|
eb82664452 | ||
|
|
36271d6b85 | ||
|
|
f839c7f2bb | ||
|
|
94dc595a81 | ||
|
|
2f4651fe6f | ||
|
|
97ae094c3c | ||
|
|
045689fab1 | ||
|
|
47d957f942 | ||
|
|
d0606a3798 | ||
|
|
bcddda3cd3 | ||
|
|
b5f50a4b9f | ||
|
|
9c77f26a8b | ||
|
|
d73d5724d8 | ||
|
|
9f21da8bde | ||
|
|
6b137b9778 | ||
|
|
c9e5eadb06 | ||
|
|
05bdef041b | ||
|
|
360a92c198 | ||
|
|
64f6b86739 | ||
|
|
dcc14a4726 | ||
|
|
2ca5f26546 | ||
|
|
24e0ea2a59 | ||
|
|
61ac7e6c1d | ||
|
|
49b28ac695 | ||
|
|
2163957299 | ||
|
|
2e1981c5a6 | ||
|
|
ab60628386 | ||
|
|
2ade6be146 | ||
|
|
721aac57a5 | ||
|
|
8618f6d7eb | ||
|
|
7543351bc9 | ||
|
|
0dfbd5fa6e | ||
|
|
9dfbc96274 | ||
|
|
ab58aa020e | ||
|
|
7f428f2eeb | ||
|
|
b4895ef730 | ||
|
|
9e05e44a14 | ||
|
|
3af3f85f82 | ||
|
|
e471d61d7a | ||
|
|
185523f66f | ||
|
|
66a83d3862 | ||
|
|
ad9d15dd21 | ||
|
|
f204b9fca0 | ||
|
|
a3e3bcd46d | ||
|
|
c693b03a64 | ||
|
|
31f15a2f09 | ||
|
|
45b5e1241c | ||
|
|
43d42b54f8 | ||
|
|
090277d7a1 | ||
|
|
766b393295 | ||
|
|
b2d647b579 | ||
|
|
1ed6844247 | ||
|
|
5276e5b4ae | ||
|
|
6587f89db1 | ||
|
|
fb262b265b | ||
|
|
95074ef304 | ||
|
|
4ac93806aa | ||
|
|
92b3149cdd | ||
|
|
98c87d4a16 | ||
|
|
9a93d5811a | ||
|
|
759e3270cc | ||
|
|
746b72166f | ||
|
|
0292df12ef | ||
|
|
1e86c07505 | ||
|
|
47b6956be9 | ||
|
|
df8708ef1e | ||
|
|
20c0be0df6 | ||
|
|
cefdc29a7f | ||
|
|
70e9b4a332 | ||
|
|
08939ac51d | ||
|
|
b5d9947408 | ||
|
|
78f0cf908e | ||
|
|
93ae5e71f7 | ||
|
|
b8b268c7cc | ||
|
|
c991bbc7e3 | ||
|
|
45bbe33929 | ||
|
|
5aaa72e8cd | ||
|
|
57345cec3b | ||
|
|
fba17a8c25 | ||
|
|
70147922ae | ||
|
|
44cc3c7809 | ||
|
|
c50a5db277 | ||
|
|
389ea2af83 | ||
|
|
4cf9bf18e9 | ||
|
|
7bc4b2c595 | ||
|
|
9075489c18 | ||
|
|
d361f5c6b0 | ||
|
|
1f95e00793 | ||
|
|
3d8899b9dc | ||
|
|
dba9aa30f7 | ||
|
|
3626943fc9 | ||
|
|
1f6a9ab556 | ||
|
|
f7085b40b1 | ||
|
|
7222bc63f7 | ||
|
|
3dacbc6bf6 | ||
|
|
9dfa29ff0f | ||
|
|
a1e67b6177 | ||
|
|
03a687c200 | ||
|
|
847c01d605 | ||
|
|
ea10cf5758 | ||
|
|
159beb138a | ||
|
|
36d6682122 | ||
|
|
25f6bea66e | ||
|
|
80461bd9fe | ||
|
|
6148edbc7d | ||
|
|
3fa529d858 | ||
|
|
9117b3cdfa | ||
|
|
d4fe5f7a83 | ||
|
|
fa6725c54a | ||
|
|
f9976005f7 | ||
|
|
c6e1b14429 | ||
|
|
30681e2e58 | ||
|
|
c15ba7d23a | ||
|
|
94d5d20281 | ||
|
|
b776308fd7 | ||
|
|
2d37920a4c | ||
|
|
ee05e0af06 | ||
|
|
2efd735243 | ||
|
|
adb0a9b6f0 | ||
|
|
c9e24c2283 | ||
|
|
041c922451 | ||
|
|
01c1096c62 | ||
|
|
2b11e45692 | ||
|
|
6163e922b3 | ||
|
|
1613495425 | ||
|
|
455c7280a4 | ||
|
|
746f8d835d | ||
|
|
d66e9a1b00 | ||
|
|
8cca75da5c | ||
|
|
8d0ff1b61d | ||
|
|
dd856b9e4a | ||
|
|
eb5ba12ba3 | ||
|
|
e2c5995a2e | ||
|
|
ef10bb2bd6 | ||
|
|
64aa5480ad | ||
|
|
816f422e21 | ||
|
|
6c0dccd9ff | ||
|
|
c2b505b78c | ||
|
|
d8fb5be9b5 | ||
|
|
51b259fdea | ||
|
|
d532b65d1c | ||
|
|
bef35b9bc3 | ||
|
|
ae261fcede | ||
|
|
c04cdff7f7 | ||
|
|
466aa5a14d | ||
|
|
4aac633413 | ||
|
|
ad328d35a2 | ||
|
|
c5140f34a7 | ||
|
|
419f6345b3 | ||
|
|
c2c53df886 | ||
|
|
b3f73bb6a9 | ||
|
|
eda5cd47ad | ||
|
|
0c906a5e6d | ||
|
|
352768053d | ||
|
|
79b1cec4f3 | ||
|
|
8d09190439 | ||
|
|
5cd0a3719e | ||
|
|
8b7cd4a0c7 | ||
|
|
937c2d3dce | ||
|
|
1fa5d273cc | ||
|
|
24fa3dbf8f | ||
|
|
c9b782fd63 | ||
|
|
e7cf560da0 | ||
|
|
86e07518ad | ||
|
|
8c71d03959 | ||
|
|
967e86f4ab | ||
|
|
730412fefe | ||
|
|
576883ddc8 | ||
|
|
992d636680 | ||
|
|
8cdd2f113f | ||
|
|
d5f935b73d | ||
|
|
84f561b251 | ||
|
|
21ac2b8f3a | ||
|
|
1790828b01 | ||
|
|
792b9090a7 | ||
|
|
8c21fad642 | ||
|
|
5136cc3c9c | ||
|
|
b78b27f517 | ||
|
|
85760ea92c | ||
|
|
c2212c719e | ||
|
|
fc8a0d0efd | ||
|
|
c052c37621 | ||
|
|
21f7cec781 | ||
|
|
64af456d29 | ||
|
|
7751c4ac1f | ||
|
|
ececdcb9c0 | ||
|
|
cb8f49aea0 | ||
|
|
e3ef7d6631 | ||
|
|
21aa1f49d7 | ||
|
|
51e80170e2 | ||
|
|
b2526ab7f6 | ||
|
|
e220447bdd | ||
|
|
ead695b101 | ||
|
|
4ea72f8f89 | ||
|
|
4ef550da9b | ||
|
|
1e660fc2a2 | ||
|
|
6adf791b3b | ||
|
|
d2a41a42e0 | ||
|
|
315549b5f8 | ||
|
|
fd4a543bab | ||
|
|
d525e56053 | ||
|
|
dab5d1f994 | ||
|
|
de3b52425c | ||
|
|
844fd58a97 | ||
|
|
de2bad51d3 | ||
|
|
1424ea3540 | ||
|
|
a8efd0ef3d | ||
|
|
1204e282d3 | ||
|
|
6588242793 | ||
|
|
b1ba9a42c6 | ||
|
|
ab0d2bf9c6 | ||
|
|
80028e41f3 | ||
|
|
2c581adc55 | ||
|
|
f0e8c1e325 | ||
|
|
a2db9de4d7 | ||
|
|
a228c62286 | ||
|
|
37d940eca6 | ||
|
|
f7c24c54a1 | ||
|
|
19ce1edc16 | ||
|
|
21b10cebe0 | ||
|
|
50435f7783 | ||
|
|
1b789de4f4 |
8
.github/workflows/lock.yml
vendored
8
.github/workflows/lock.yml
vendored
@@ -13,11 +13,3 @@ jobs:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: 45
|
||||
pr-lock-inactive-days: 45
|
||||
issue-lock-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
pr-lock-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
|
||||
2
.github/workflows/mac.yml
vendored
2
.github/workflows/mac.yml
vendored
@@ -13,6 +13,7 @@ on:
|
||||
- '!.github/workflows/mac.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/docker/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
@@ -30,6 +31,7 @@ on:
|
||||
- '!.github/workflows/mac.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/docker/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
|
||||
7
.github/workflows/win.yml
vendored
7
.github/workflows/win.yml
vendored
@@ -185,7 +185,7 @@ jobs:
|
||||
- name: MozJPEG.
|
||||
shell: cmd
|
||||
run: |
|
||||
git clone -b v4.0.1-rc2 %GIT%/mozilla/mozjpeg.git
|
||||
git clone -b v4.0.3 %GIT%/mozilla/mozjpeg.git
|
||||
cd mozjpeg
|
||||
cmake . ^
|
||||
-G "Visual Studio 16 2019" ^
|
||||
@@ -236,10 +236,10 @@ jobs:
|
||||
|
||||
git clone https://chromium.googlesource.com/breakpad/breakpad
|
||||
cd breakpad
|
||||
git checkout bc8fb886
|
||||
git checkout dfcb7b6799
|
||||
git apply ../patches/breakpad.diff
|
||||
cd src
|
||||
git clone %GIT%/google/googletest testing
|
||||
git clone -b release-1.11.0 %GIT%/google/googletest testing
|
||||
cd client\windows
|
||||
call gyp --no-circular-check breakpad_client.gyp --format=ninja
|
||||
cd ..\..
|
||||
@@ -261,6 +261,7 @@ jobs:
|
||||
run: |
|
||||
git clone -b v1.3.1 %GIT%/xiph/opus.git
|
||||
cd opus
|
||||
git cherry-pick 927de8453c
|
||||
cmake -B out . ^
|
||||
-A Win32 ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^
|
||||
|
||||
@@ -39,6 +39,7 @@ include(cmake/init_target.cmake)
|
||||
include(cmake/generate_target.cmake)
|
||||
include(cmake/nuget.cmake)
|
||||
include(cmake/validate_d3d_compiler.cmake)
|
||||
include(cmake/target_prepare_qrc.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
|
||||
## Third-party
|
||||
|
||||
* Qt 5.15.2, 5.6.2 and 5.3.2 slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
|
||||
* Qt 6 ([LGPL](http://doc.qt.io/qt-6/lgpl.html)) and Qt 5.15.2 ([LGPL](http://doc.qt.io/qt-5/lgpl.html)) slightly patched
|
||||
* OpenSSL 1.1.1 and 1.0.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
|
||||
* WebRTC ([New BSD License](https://github.com/desktop-app/tg_owt/blob/master/LICENSE))
|
||||
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
add_executable(Telegram WIN32 MACOSX_BUNDLE)
|
||||
init_target(Telegram)
|
||||
init_non_host_target(Telegram)
|
||||
|
||||
add_subdirectory(lib_rpl)
|
||||
add_subdirectory(lib_crl)
|
||||
@@ -42,12 +42,16 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
generate_midl(Telegram ${src_loc}
|
||||
platform/win/windows_quiethours.idl
|
||||
platform/win/windows_toastactivator.idl
|
||||
)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON)
|
||||
target_prepare_qrc(Telegram)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
@@ -111,6 +115,8 @@ PRIVATE
|
||||
api/api_cloud_password.cpp
|
||||
api/api_cloud_password.h
|
||||
api/api_common.h
|
||||
api/api_confirm_phone.cpp
|
||||
api/api_confirm_phone.h
|
||||
api/api_editing.cpp
|
||||
api/api_editing.h
|
||||
api/api_global_privacy.cpp
|
||||
@@ -121,6 +127,10 @@ PRIVATE
|
||||
api/api_invite_links.h
|
||||
api/api_media.cpp
|
||||
api/api_media.h
|
||||
api/api_peer_photo.cpp
|
||||
api/api_peer_photo.h
|
||||
api/api_polls.cpp
|
||||
api/api_polls.h
|
||||
api/api_self_destruct.cpp
|
||||
api/api_self_destruct.h
|
||||
api/api_send_progress.cpp
|
||||
@@ -139,6 +149,8 @@ PRIVATE
|
||||
api/api_updates.h
|
||||
api/api_user_privacy.cpp
|
||||
api/api_user_privacy.h
|
||||
api/api_views.cpp
|
||||
api/api_views.h
|
||||
api/api_who_read.cpp
|
||||
api/api_who_read.h
|
||||
boxes/filters/edit_filter_box.cpp
|
||||
@@ -155,20 +167,29 @@ PRIVATE
|
||||
boxes/peers/edit_participant_box.h
|
||||
boxes/peers/edit_participants_box.cpp
|
||||
boxes/peers/edit_participants_box.h
|
||||
boxes/peers/edit_peer_common.h
|
||||
boxes/peers/edit_peer_info_box.cpp
|
||||
boxes/peers/edit_peer_info_box.h
|
||||
boxes/peers/edit_peer_invite_link.cpp
|
||||
boxes/peers/edit_peer_invite_link.h
|
||||
boxes/peers/edit_peer_invite_links.cpp
|
||||
boxes/peers/edit_peer_invite_links.h
|
||||
boxes/peers/edit_peer_type_box.cpp
|
||||
boxes/peers/edit_peer_type_box.h
|
||||
boxes/peers/edit_peer_history_visibility_box.cpp
|
||||
boxes/peers/edit_peer_history_visibility_box.h
|
||||
boxes/peers/edit_peer_permissions_box.cpp
|
||||
boxes/peers/edit_peer_permissions_box.h
|
||||
boxes/peers/edit_peer_requests_box.cpp
|
||||
boxes/peers/edit_peer_requests_box.h
|
||||
boxes/peers/edit_peer_type_box.cpp
|
||||
boxes/peers/edit_peer_type_box.h
|
||||
boxes/peers/peer_short_info_box.cpp
|
||||
boxes/peers/peer_short_info_box.h
|
||||
boxes/peers/prepare_short_info_box.cpp
|
||||
boxes/peers/prepare_short_info_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
boxes/about_sponsored_box.h
|
||||
boxes/abstract_box.cpp
|
||||
boxes/abstract_box.h
|
||||
boxes/add_contact_box.cpp
|
||||
@@ -183,14 +204,12 @@ PRIVATE
|
||||
boxes/background_preview_box.h
|
||||
boxes/change_phone_box.cpp
|
||||
boxes/change_phone_box.h
|
||||
boxes/confirm_box.cpp
|
||||
boxes/confirm_box.h
|
||||
boxes/confirm_phone_box.cpp
|
||||
boxes/confirm_phone_box.h
|
||||
boxes/connection_box.cpp
|
||||
boxes/connection_box.h
|
||||
boxes/create_poll_box.cpp
|
||||
boxes/create_poll_box.h
|
||||
boxes/delete_messages_box.cpp
|
||||
boxes/delete_messages_box.h
|
||||
boxes/dictionaries_manager.cpp
|
||||
boxes/dictionaries_manager.h
|
||||
boxes/download_path_box.cpp
|
||||
@@ -205,6 +224,8 @@ PRIVATE
|
||||
boxes/language_box.h
|
||||
boxes/local_storage_box.cpp
|
||||
boxes/local_storage_box.h
|
||||
boxes/max_invite_box.cpp
|
||||
boxes/max_invite_box.h
|
||||
boxes/mute_settings_box.cpp
|
||||
boxes/mute_settings_box.h
|
||||
boxes/peer_list_box.cpp
|
||||
@@ -215,8 +236,10 @@ PRIVATE
|
||||
boxes/peer_lists_box.h
|
||||
boxes/passcode_box.cpp
|
||||
boxes/passcode_box.h
|
||||
boxes/rate_call_box.cpp
|
||||
boxes/rate_call_box.h
|
||||
boxes/phone_banned_box.cpp
|
||||
boxes/phone_banned_box.h
|
||||
boxes/pin_messages_box.cpp
|
||||
boxes/pin_messages_box.h
|
||||
boxes/self_destruction_box.cpp
|
||||
boxes/self_destruction_box.h
|
||||
boxes/send_files_box.cpp
|
||||
@@ -235,6 +258,8 @@ PRIVATE
|
||||
boxes/username_box.h
|
||||
calls/group/calls_choose_join_as.cpp
|
||||
calls/group/calls_choose_join_as.h
|
||||
calls/group/calls_cover_item.cpp
|
||||
calls/group/calls_cover_item.h
|
||||
calls/group/calls_group_call.cpp
|
||||
calls/group/calls_group_call.h
|
||||
calls/group/calls_group_common.cpp
|
||||
@@ -400,6 +425,7 @@ PRIVATE
|
||||
data/data_file_origin.cpp
|
||||
data/data_file_origin.h
|
||||
data/data_flags.h
|
||||
data/data_game.cpp
|
||||
data/data_game.h
|
||||
data/data_group_call.cpp
|
||||
data/data_group_call.h
|
||||
@@ -415,6 +441,7 @@ PRIVATE
|
||||
data/data_media_types.h
|
||||
data/data_messages.cpp
|
||||
data/data_messages.h
|
||||
data/data_msg_id.h
|
||||
data/data_notify_settings.cpp
|
||||
data/data_notify_settings.h
|
||||
data/data_peer.cpp
|
||||
@@ -447,6 +474,8 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_sponsored_messages.cpp
|
||||
data/data_sponsored_messages.h
|
||||
data/data_streaming.cpp
|
||||
data/data_streaming.h
|
||||
data/data_types.cpp
|
||||
@@ -467,8 +496,6 @@ PRIVATE
|
||||
dialogs/dialogs_inner_widget.h
|
||||
dialogs/dialogs_key.cpp
|
||||
dialogs/dialogs_key.h
|
||||
dialogs/dialogs_layout.cpp
|
||||
dialogs/dialogs_layout.h
|
||||
dialogs/dialogs_list.cpp
|
||||
dialogs/dialogs_list.h
|
||||
dialogs/dialogs_main_list.cpp
|
||||
@@ -481,6 +508,10 @@ PRIVATE
|
||||
dialogs/dialogs_search_from_controllers.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
@@ -596,8 +627,8 @@ PRIVATE
|
||||
history/view/history_view_emoji_interactions.h
|
||||
history/view/history_view_empty_list_bubble.cpp
|
||||
history/view/history_view_empty_list_bubble.h
|
||||
history/view/history_view_group_call_tracker.cpp
|
||||
history/view/history_view_group_call_tracker.h
|
||||
history/view/history_view_group_call_bar.cpp
|
||||
history/view/history_view_group_call_bar.h
|
||||
history/view/history_view_list_widget.cpp
|
||||
history/view/history_view_list_widget.h
|
||||
history/view/history_view_message.cpp
|
||||
@@ -611,6 +642,8 @@ PRIVATE
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_requests_bar.cpp
|
||||
history/view/history_view_requests_bar.h
|
||||
history/view/history_view_schedule_box.cpp
|
||||
history/view/history_view_schedule_box.h
|
||||
history/view/history_view_scheduled_section.cpp
|
||||
@@ -621,6 +654,8 @@ PRIVATE
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_view_button.cpp
|
||||
history/view/history_view_view_button.h
|
||||
history/view/history_view_webpage_preview.cpp
|
||||
history/view/history_view_webpage_preview.h
|
||||
history/history.cpp
|
||||
@@ -631,6 +666,10 @@ PRIVATE
|
||||
history/history_item.h
|
||||
history/history_item_components.cpp
|
||||
history/history_item_components.h
|
||||
history/history_item_edition.cpp
|
||||
history/history_item_edition.h
|
||||
history/history_item_reply_markup.cpp
|
||||
history/history_item_reply_markup.h
|
||||
history/history_item_text.cpp
|
||||
history/history_item_text.h
|
||||
history/history_inner_widget.cpp
|
||||
@@ -943,6 +982,8 @@ PRIVATE
|
||||
platform/win/windows_dlls.h
|
||||
platform/win/windows_event_filter.cpp
|
||||
platform/win/windows_event_filter.h
|
||||
platform/win/windows_toast_activator.cpp
|
||||
platform/win/windows_toast_activator.h
|
||||
platform/platform_audio.h
|
||||
platform/platform_file_utilities.h
|
||||
platform/platform_launcher.h
|
||||
@@ -1040,6 +1081,8 @@ PRIVATE
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
ui/chat/attach/attach_item_single_media_preview.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
@@ -1052,10 +1095,6 @@ PRIVATE
|
||||
ui/image/image_location.h
|
||||
ui/image/image_location_factory.cpp
|
||||
ui/image/image_location_factory.h
|
||||
ui/widgets/continuous_sliders.cpp
|
||||
ui/widgets/continuous_sliders.h
|
||||
ui/widgets/discrete_sliders.cpp
|
||||
ui/widgets/discrete_sliders.h
|
||||
ui/widgets/level_meter.cpp
|
||||
ui/widgets/level_meter.h
|
||||
ui/widgets/multi_select.cpp
|
||||
@@ -1221,8 +1260,7 @@ elseif (APPLE)
|
||||
endif()
|
||||
|
||||
set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets)
|
||||
set_target_properties(Telegram PROPERTIES RESOURCE ${icons_path})
|
||||
target_sources(Telegram PRIVATE ${icons_path})
|
||||
target_add_resource(Telegram ${icons_path})
|
||||
|
||||
set(lang_packs
|
||||
en
|
||||
@@ -1243,6 +1281,11 @@ elseif (APPLE)
|
||||
source_group(TREE ${res_loc} PREFIX Resources FILES ${strings_path})
|
||||
endforeach()
|
||||
|
||||
add_custom_command(TARGET Telegram
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
)
|
||||
if (NOT build_macstore)
|
||||
add_custom_command(TARGET Telegram
|
||||
PRE_LINK
|
||||
@@ -1250,10 +1293,15 @@ elseif (APPLE)
|
||||
COMMAND cp $<TARGET_FILE:Updater> $<TARGET_FILE_DIR:Telegram>/../Frameworks/
|
||||
)
|
||||
if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS)
|
||||
if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64")
|
||||
set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}")
|
||||
else()
|
||||
set(crashpad_dir_part "")
|
||||
endif()
|
||||
add_custom_command(TARGET Telegram
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Helpers
|
||||
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
|
||||
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>${crashpad_dir_part}/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
@@ -1297,6 +1345,8 @@ if (build_macstore)
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Frameworks
|
||||
COMMAND cp -a ${libs_loc}/breakpad/src/client/mac/build/Release/Breakpad.framework $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework/Resources/crash_report_sender.app
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework/Resources/Inspector
|
||||
)
|
||||
else()
|
||||
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
||||
@@ -1316,7 +1366,6 @@ set_target_properties(Telegram PROPERTIES
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${bundle_identifier}
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${desktop_app_version_string}
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME ${output_name}
|
||||
XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT $<$<NOT:$<CONFIG:Debug>>:dwarf-with-dsym>
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
|
||||
XCODE_ATTRIBUTE_COMBINE_HIDPI_IMAGES YES
|
||||
@@ -1368,18 +1417,20 @@ if (WIN32)
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
|
||||
add_executable(Updater WIN32)
|
||||
init_target(Updater)
|
||||
init_non_host_target(Updater)
|
||||
|
||||
add_dependencies(Telegram Updater)
|
||||
|
||||
@@ -1391,7 +1442,9 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
|
||||
_other/updater.h
|
||||
)
|
||||
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
set_target_properties(Updater PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${output_folder}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
get_filename_component(lib_base_loc lib_base REALPATH)
|
||||
|
||||
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 165 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 475 B |
@@ -978,6 +978,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_removed_users" = "Removed users";
|
||||
"lng_manage_peer_permissions" = "Permissions";
|
||||
"lng_manage_peer_invite_links" = "Invite links";
|
||||
"lng_manage_peer_requests" = "Member Requests";
|
||||
"lng_manage_peer_requests_channel" = "Subscriber Requests";
|
||||
|
||||
"lng_manage_peer_group_type" = "Group type";
|
||||
"lng_manage_peer_channel_type" = "Channel type";
|
||||
@@ -1158,6 +1160,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_user_left" = "{from} left the group";
|
||||
"lng_action_user_joined" = "{from} joined the group";
|
||||
"lng_action_user_joined_by_link" = "{from} joined the group via invite link";
|
||||
"lng_action_user_joined_by_request" = "{from} was accepted to the group";
|
||||
"lng_action_you_joined_by_request" = "Your request to join the group was approved";
|
||||
"lng_action_you_joined_by_request_channel" = "Your request to join the channel was approved";
|
||||
"lng_action_user_registered" = "{from} just joined Telegram";
|
||||
"lng_action_removed_photo" = "{from} removed group photo";
|
||||
"lng_action_removed_photo_channel" = "Channel photo removed";
|
||||
@@ -1269,6 +1274,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_joined#other" = "{count} joined";
|
||||
"lng_group_invite_remaining#one" = "{count} remaining";
|
||||
"lng_group_invite_remaining#other" = "{count} remaining";
|
||||
"lng_group_invite_requested#one" = "{count} requested";
|
||||
"lng_group_invite_requested#other" = "{count} requested";
|
||||
"lng_group_invite_requested_full#one" = "{count} requested to join";
|
||||
"lng_group_invite_requested_full#other" = "{count} requested to join";
|
||||
"lng_group_invite_can_join#one" = "{count} can join";
|
||||
"lng_group_invite_can_join#other" = "{count} can join";
|
||||
"lng_group_invite_days_left#one" = "{count} day left";
|
||||
@@ -1298,6 +1307,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_link_expired" = "Expired";
|
||||
"lng_group_invite_edit_title" = "Edit Link";
|
||||
"lng_group_invite_new_title" = "New Link";
|
||||
"lng_group_invite_label_header" = "Link Name (optional)";
|
||||
"lng_group_invite_label_about" = "Only admins will see this name.";
|
||||
"lng_group_invite_expire_title" = "Limit by time period";
|
||||
"lng_group_invite_expire_about" = "You can make the link expire after a certain time.";
|
||||
"lng_group_invite_expire_never" = "No limit";
|
||||
@@ -1320,6 +1331,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_qr_title" = "Invite by QR Code";
|
||||
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
|
||||
"lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
|
||||
"lng_group_invite_request_approve" = "Request admin approval";
|
||||
"lng_group_invite_about_approve" = "New users will be able to join the group only after having been approved by the admins.";
|
||||
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
|
||||
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
|
||||
"lng_group_invite_about_no_approve_channel" = "New users will be able to join the channel without being approved by the admins.";
|
||||
|
||||
"lng_group_request_to_join" = "Request to Join";
|
||||
"lng_group_request_about" = "This group accepts new members only after they are approved by its admins.";
|
||||
"lng_group_request_about_channel" = "This channel accepts new subscribers only after they are approved by its admins.";
|
||||
"lng_group_request_sent" = "You will be added to the group once its admins approve your request.";
|
||||
"lng_group_request_sent_channel" = "You will be added to the channel once its admins approve your request.";
|
||||
"lng_group_requests_pending#one" = "{count} user requested to join";
|
||||
"lng_group_requests_pending#other" = "{count} users requested to join";
|
||||
"lng_group_requests_pending_user" = "{user} requested to join";
|
||||
"lng_group_requests_status_today" = "requested to join today at {time}";
|
||||
"lng_group_requests_status_yesterday" = "requested to join yesterday at {time}";
|
||||
"lng_group_requests_status_date_time" = "requested to join {date} at {time}";
|
||||
"lng_group_requests_add" = "Add to Group";
|
||||
"lng_group_requests_add_channel" = "Add to Channel";
|
||||
"lng_group_requests_dismiss" = "Dismiss";
|
||||
"lng_group_requests_was_added" = "{user} has been added to the group.";
|
||||
"lng_group_requests_was_added_channel" = "{user} has been added to the channel.";
|
||||
"lng_group_requests_none" = "You have no pending requests\nto join your group.";
|
||||
"lng_group_requests_none_channel" = "You have no pending requests\nto join your channel.";
|
||||
|
||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||
@@ -1335,6 +1370,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forwarded_imported" = "This message was imported from another app. It may not be real.";
|
||||
"lng_signed_author" = "Author: {user}";
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_sponsored" = "sponsored";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
@@ -1508,6 +1544,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
|
||||
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
|
||||
"lng_will_be_notified" = "Members will be notified when you post";
|
||||
"lng_wont_be_notified" = "Members will not be notified when you post";
|
||||
"lng_willbe_history" = "Please select a chat to start messaging";
|
||||
@@ -2448,6 +2485,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_participant_joined_channel" = "{from} joined the channel";
|
||||
"lng_admin_log_participant_joined_by_link" = "{from} joined the group via {link}";
|
||||
"lng_admin_log_participant_joined_by_link_channel" = "{from} joined the channel via {link}";
|
||||
"lng_admin_log_participant_approved_by_link" = "{from} was approved to join the group via {link} by {user}";
|
||||
"lng_admin_log_participant_approved_by_link_channel" = "{from} was approved to join the channel via {link} by {user}";
|
||||
"lng_admin_log_revoke_invite_link" = "{from} revoked invite link {link}";
|
||||
"lng_admin_log_delete_invite_link" = "{from} deleted invite link {link}";
|
||||
"lng_admin_log_participant_left" = "{from} left the group";
|
||||
@@ -2492,6 +2531,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_edited_invite_link" = "edited invite link {link}";
|
||||
"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}";
|
||||
"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}";
|
||||
"lng_admin_log_invite_link_label" = "Name: {previous} -> {limit}";
|
||||
"lng_admin_log_invite_link_request_needed" = "Now admin approval is required to join.";
|
||||
"lng_admin_log_invite_link_request_not_needed" = "Now admin approval is not required to join.";
|
||||
"lng_admin_log_restricted_forever" = "indefinitely";
|
||||
"lng_admin_log_restricted_until" = "until {date}";
|
||||
"lng_admin_log_banned_view_messages" = "Read messages";
|
||||
@@ -2871,6 +2913,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
"lng_chat_theme_reset" = "Reset Theme";
|
||||
"lng_chat_theme_dont" = "Do Not Set Theme";
|
||||
"lng_chat_theme_title" = "Select theme";
|
||||
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
@@ -2880,6 +2930,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_voice_speed_fast" = "Fast";
|
||||
"lng_voice_speed_very_fast" = "Very fast";
|
||||
|
||||
"lng_view_button_user" = "View user";
|
||||
"lng_view_button_bot" = "View bot";
|
||||
"lng_view_button_group" = "View group";
|
||||
"lng_view_button_channel" = "View channel";
|
||||
"lng_view_button_background" = "View background";
|
||||
"lng_view_button_theme" = "View theme";
|
||||
"lng_view_button_message" = "View message";
|
||||
"lng_view_button_voice_chat" = "Voice chat";
|
||||
"lng_view_button_voice_chat_channel" = "Live stream";
|
||||
"lng_view_button_request_join" = "Request to Join";
|
||||
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
|
||||
"lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
@@ -128,8 +128,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat;
|
||||
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#4dbdc099 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull;
|
||||
channelFull#e9b27a17 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull;
|
||||
chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> = ChatFull;
|
||||
channelFull#59cff963 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> = ChatFull;
|
||||
|
||||
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
|
||||
@@ -188,6 +188,7 @@ messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector<long> =
|
||||
messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
|
||||
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
|
||||
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
|
||||
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
|
||||
|
||||
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
@@ -378,6 +379,8 @@ updateChannelParticipant#985d3abb flags:# channel_id:long date:int actor_id:long
|
||||
updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update;
|
||||
updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
|
||||
updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
|
||||
updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
|
||||
updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -549,10 +552,10 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
|
||||
|
||||
receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
|
||||
|
||||
chatInviteExported#b18105e8 flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite;
|
||||
chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite;
|
||||
|
||||
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
|
||||
chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
|
||||
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
|
||||
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
|
||||
|
||||
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
|
||||
@@ -625,7 +628,7 @@ channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter;
|
||||
channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector<MessageRange> = ChannelMessagesFilter;
|
||||
|
||||
channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant;
|
||||
channelParticipantSelf#28a8bc67 user_id:long inviter_id:long date:int = ChannelParticipant;
|
||||
channelParticipantSelf#35a8bfa7 flags:# via_invite:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant;
|
||||
channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
|
||||
channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
|
||||
channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant;
|
||||
@@ -908,6 +911,7 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit
|
||||
channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction;
|
||||
|
||||
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
|
||||
|
||||
@@ -1122,7 +1126,7 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti
|
||||
inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
|
||||
inputThemeSlug#f5890df1 slug:string = InputTheme;
|
||||
|
||||
theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme;
|
||||
theme#a00e67d6 flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?Vector<ThemeSettings> emoticon:flags.6?string installs_count:flags.4?int = Theme;
|
||||
|
||||
account.themesNotModified#f41eb622 = account.Themes;
|
||||
account.themes#9a3d8c6d hash:long themes:Vector<Theme> = account.Themes;
|
||||
@@ -1234,7 +1238,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true
|
||||
|
||||
messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector<int> = messages.AffectedFoundMessages;
|
||||
|
||||
chatInviteImporter#b5cd5f4 user_id:long date:int = ChatInviteImporter;
|
||||
chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter;
|
||||
|
||||
messages.exportedChatInvites#bdc62dcc count:int invites:Vector<ExportedChatInvite> users:Vector<User> = messages.ExportedChatInvites;
|
||||
|
||||
@@ -1271,15 +1275,18 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
|
||||
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
|
||||
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
|
||||
|
||||
chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme;
|
||||
|
||||
account.chatThemesNotModified#e011e1c4 = account.ChatThemes;
|
||||
account.chatThemes#fe4cbebd hash:int themes:Vector<ChatTheme> = account.ChatThemes;
|
||||
|
||||
sponsoredMessage#2a3c381f flags:# random_id:bytes from_id:Peer start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
|
||||
sponsoredMessage#d151e19a flags:# random_id:bytes from_id:Peer channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
|
||||
|
||||
messages.sponsoredMessages#65a4c7d5 messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
|
||||
searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
|
||||
|
||||
messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector<SearchResultsCalendarPeriod> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SearchResultsCalendar;
|
||||
|
||||
searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosition;
|
||||
|
||||
messages.searchResultsPositions#53b22baf count:int positions:Vector<SearchResultsPosition> = messages.SearchResultsPositions;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1366,10 +1373,10 @@ account.resetWallPapers#bb3b9804 = Bool;
|
||||
account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings;
|
||||
account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool;
|
||||
account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;
|
||||
account.createTheme#8432c21f flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
|
||||
account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme;
|
||||
account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
|
||||
account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
|
||||
account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
|
||||
account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool;
|
||||
account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool;
|
||||
account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme;
|
||||
account.getThemes#7206e458 format:string hash:long = account.Themes;
|
||||
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
|
||||
@@ -1380,7 +1387,7 @@ account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = Globa
|
||||
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
|
||||
account.resetPassword#9308ce1b = account.ResetPasswordResult;
|
||||
account.declinePasswordReset#4c9409f6 = Bool;
|
||||
account.getChatThemes#d6d71d7b hash:int = account.ChatThemes;
|
||||
account.getChatThemes#d638de89 hash:long = account.Themes;
|
||||
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
@@ -1412,7 +1419,7 @@ messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags
|
||||
messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||
messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
|
||||
messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory;
|
||||
messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
|
||||
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
|
||||
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
|
||||
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
|
||||
@@ -1444,7 +1451,7 @@ messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages
|
||||
messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers;
|
||||
messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers;
|
||||
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
|
||||
messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite;
|
||||
messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite;
|
||||
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
|
||||
messages.importChatInvite#6c50051c hash:string = Updates;
|
||||
messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
|
||||
@@ -1508,7 +1515,6 @@ messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?
|
||||
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
|
||||
messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
|
||||
messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
|
||||
messages.getStatsURL#812c2ae6 flags:# dark:flags.0?true peer:InputPeer params:string = StatsURL;
|
||||
messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
|
||||
messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
|
||||
messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
|
||||
@@ -1542,15 +1548,18 @@ messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:st
|
||||
messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool;
|
||||
messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
|
||||
messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite;
|
||||
messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite;
|
||||
messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
|
||||
messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool;
|
||||
messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool;
|
||||
messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites;
|
||||
messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
|
||||
messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
|
||||
messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates;
|
||||
messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer;
|
||||
messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates;
|
||||
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
|
||||
messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar;
|
||||
messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions;
|
||||
messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
@@ -1698,4 +1707,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 133
|
||||
// LAYER 134
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.1.2.0" />
|
||||
Version="3.2.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,2,0
|
||||
PRODUCTVERSION 3,1,2,0
|
||||
FILEVERSION 3,2,0,0
|
||||
PRODUCTVERSION 3,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.1.2.0"
|
||||
VALUE "FileVersion", "3.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.2.0"
|
||||
VALUE "ProductVersion", "3.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,2,0
|
||||
PRODUCTVERSION 3,1,2,0
|
||||
FILEVERSION 3,2,0,0
|
||||
PRODUCTVERSION 3,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.1.2.0"
|
||||
VALUE "FileVersion", "3.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.2.0"
|
||||
VALUE "ProductVersion", "3.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -161,6 +161,7 @@ int main(int argc, char *argv[])
|
||||
QString remove;
|
||||
int version = 0;
|
||||
[[maybe_unused]] bool targetwin64 = false;
|
||||
[[maybe_unused]] bool targetarmac = false;
|
||||
QFileInfoList files;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
if (string("-path") == argv[i] && i + 1 < argc) {
|
||||
@@ -170,6 +171,12 @@ int main(int argc, char *argv[])
|
||||
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
|
||||
} else if (string("-target") == argv[i] && i + 1 < argc) {
|
||||
targetwin64 = (string("win64") == argv[i + 1]);
|
||||
} else if (string("-arch") == argv[i] && i + 1 < argc) {
|
||||
targetarmac = (string("arm64") == argv[i + 1]);
|
||||
if (!targetarmac && string("x86_64") != argv[i + 1]) {
|
||||
cout << "Bad -arch param value passed: " << argv[i + 1] << "\n";
|
||||
return -1;
|
||||
}
|
||||
} else if (string("-version") == argv[i] && i + 1 < argc) {
|
||||
version = QString(argv[i + 1]).toInt();
|
||||
} else if (string("-beta") == argv[i]) {
|
||||
@@ -494,7 +501,7 @@ int main(int argc, char *argv[])
|
||||
#ifdef Q_OS_WIN
|
||||
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#elif defined Q_OS_MAC
|
||||
QString outName(QString("tmacupd%1").arg(AlphaVersion ? AlphaVersion : version));
|
||||
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#elif defined Q_OS_UNIX
|
||||
QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version));
|
||||
#else
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_attached_stickers.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -37,7 +37,7 @@ void AttachedStickers::request(
|
||||
}
|
||||
if (result.v.isEmpty()) {
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
return;
|
||||
} else if (result.v.size() > 1) {
|
||||
strongController->show(
|
||||
@@ -63,7 +63,7 @@ void AttachedStickers::request(
|
||||
_requestId = 0;
|
||||
if (const auto strongController = weak.get()) {
|
||||
strongController->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
|
||||
&& lastDate.weekNumber() == nowDate.weekNumber()) {
|
||||
result.active = langDayOfWeek(lastDate);
|
||||
} else {
|
||||
result.active = lastDate.toString(qsl("d.MM.yy"));
|
||||
result.active = lastDate.toString(cDateFormat());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ BlockedPeers::Slice TLToSlice(
|
||||
Data::Session &owner) {
|
||||
const auto create = [&](int count, const QVector<MTPPeerBlocked> &list) {
|
||||
auto slice = BlockedPeers::Slice();
|
||||
slice.total = std::max(count, list.size());
|
||||
slice.total = std::max(count, int(list.size()));
|
||||
slice.list.reserve(list.size());
|
||||
for (const auto &contact : list) {
|
||||
contact.match([&](const MTPDpeerBlocked &data) {
|
||||
|
||||
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "api/api_send_progress.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -91,7 +91,7 @@ void SendBotCallbackData(
|
||||
result.match([&](const MTPDmessages_botCallbackAnswer &data) {
|
||||
if (const auto message = data.vmessage()) {
|
||||
if (data.is_alert()) {
|
||||
Ui::show(Box<InformBox>(qs(*message)));
|
||||
Ui::show(Box<Ui::InformBox>(qs(*message)));
|
||||
} else {
|
||||
if (withPassword) {
|
||||
Ui::hideLayer();
|
||||
|
||||
@@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -38,11 +38,12 @@ void CheckChatInvite(
|
||||
if (!strongController) {
|
||||
return;
|
||||
}
|
||||
const auto isGroup = !data.is_broadcast();
|
||||
const auto box = strongController->show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
[=] { session->api().importChatInvite(hash); }));
|
||||
[=] { session->api().importChatInvite(hash, isGroup); }));
|
||||
if (invitePeekChannel) {
|
||||
box->boxClosing(
|
||||
) | rpl::filter([=] {
|
||||
@@ -86,7 +87,7 @@ void CheckChatInvite(
|
||||
Core::App().hideMediaView();
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->show(
|
||||
Box<InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_group_invite_bad_link(tr::now)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -103,8 +104,11 @@ ConfirmInviteBox::ConfirmInviteBox(
|
||||
, _submit(std::move(submit))
|
||||
, _title(this, st::confirmInviteTitle)
|
||||
, _status(this, st::confirmInviteStatus)
|
||||
, _about(this, st::confirmInviteAbout)
|
||||
, _aboutRequests(this, st::confirmInviteStatus)
|
||||
, _participants(GetParticipants(_session, data))
|
||||
, _isChannel(data.is_channel() && !data.is_megagroup()) {
|
||||
, _isChannel(data.is_channel() && !data.is_megagroup())
|
||||
, _requestApprove(data.is_request_needed()) {
|
||||
const auto title = qs(data.vtitle());
|
||||
const auto count = data.vparticipants_count().v;
|
||||
const auto status = [&] {
|
||||
@@ -125,6 +129,18 @@ ConfirmInviteBox::ConfirmInviteBox(
|
||||
}();
|
||||
_title->setText(title);
|
||||
_status->setText(status);
|
||||
if (const auto v = qs(data.vabout().value_or_empty()); !v.isEmpty()) {
|
||||
_about->setText(v);
|
||||
} else {
|
||||
_about.destroy();
|
||||
}
|
||||
if (_requestApprove) {
|
||||
_aboutRequests->setText(_isChannel
|
||||
? tr::lng_group_request_about_channel(tr::now)
|
||||
: tr::lng_group_request_about(tr::now));
|
||||
} else {
|
||||
_aboutRequests.destroy();
|
||||
}
|
||||
|
||||
const auto photo = _session->data().processPhoto(data.vphoto());
|
||||
if (!photo->isNull()) {
|
||||
@@ -166,7 +182,9 @@ auto ConfirmInviteBox::GetParticipants(
|
||||
|
||||
void ConfirmInviteBox::prepare() {
|
||||
addButton(
|
||||
(_isChannel
|
||||
(_requestApprove
|
||||
? tr::lng_group_request_to_join()
|
||||
: _isChannel
|
||||
? tr::lng_profile_join_channel()
|
||||
: tr::lng_profile_join_group()),
|
||||
_submit);
|
||||
@@ -178,7 +196,7 @@ void ConfirmInviteBox::prepare() {
|
||||
|
||||
auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom();
|
||||
if (!_participants.empty()) {
|
||||
int skip = (st::boxWideWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
|
||||
int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5;
|
||||
int padding = skip / 2;
|
||||
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
@@ -195,6 +213,16 @@ void ConfirmInviteBox::prepare() {
|
||||
|
||||
newHeight += st::confirmInviteUserHeight;
|
||||
}
|
||||
if (_about) {
|
||||
const auto padding = st::confirmInviteAboutPadding;
|
||||
_about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
|
||||
newHeight += padding.top() + _about->height() + padding.bottom();
|
||||
}
|
||||
if (_aboutRequests) {
|
||||
const auto padding = st::confirmInviteAboutRequestsPadding;
|
||||
_aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right());
|
||||
newHeight += padding.top() + _aboutRequests->height() + padding.bottom();
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
|
||||
@@ -202,6 +230,19 @@ void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop);
|
||||
_status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop);
|
||||
auto bottom = _status->y()
|
||||
+ _status->height()
|
||||
+ st::boxPadding.bottom()
|
||||
+ (_participants.empty() ? 0 : st::confirmInviteUserHeight);
|
||||
if (_about) {
|
||||
const auto padding = st::confirmInviteAboutPadding;
|
||||
_about->move((width() - _about->width()) / 2, bottom + padding.top());
|
||||
bottom += padding.top() + _about->height() + padding.bottom();
|
||||
}
|
||||
if (_aboutRequests) {
|
||||
const auto padding = st::confirmInviteAboutRequestsPadding;
|
||||
_aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top());
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
@@ -68,10 +68,13 @@ private:
|
||||
Fn<void()> _submit;
|
||||
object_ptr<Ui::FlatLabel> _title;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
object_ptr<Ui::FlatLabel> _about;
|
||||
object_ptr<Ui::FlatLabel> _aboutRequests;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
|
||||
std::vector<Participant> _participants;
|
||||
bool _isChannel = false;
|
||||
bool _requestApprove = false;
|
||||
|
||||
int _userWidth = 0;
|
||||
|
||||
|
||||
125
Telegram/SourceFiles/api/api_confirm_phone.cpp
Normal file
125
Telegram/SourceFiles/api/api_confirm_phone.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_confirm_phone.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_phone_box.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
ConfirmPhone::ConfirmPhone(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void ConfirmPhone::resolve(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &phone,
|
||||
const QString &hash) {
|
||||
if (_sendRequestId) {
|
||||
return;
|
||||
}
|
||||
_sendRequestId = _api.request(MTPaccount_SendConfirmPhoneCode(
|
||||
MTP_string(hash),
|
||||
MTP_codeSettings(MTP_flags(0))
|
||||
)).done([=](const MTPauth_SentCode &result) {
|
||||
_sendRequestId = 0;
|
||||
|
||||
result.match([&](const MTPDauth_sentCode &data) {
|
||||
const auto sentCodeLength = data.vtype().match([&](
|
||||
const MTPDauth_sentCodeTypeApp &data) {
|
||||
LOG(("Error: should not be in-app code!"));
|
||||
return 0;
|
||||
}, [&](const MTPDauth_sentCodeTypeSms &data) {
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &data) {
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &data) {
|
||||
LOG(("Error: should not be flashcall!"));
|
||||
return 0;
|
||||
});
|
||||
const auto phoneHash = qs(data.vphone_code_hash());
|
||||
const auto timeout = [&]() -> std::optional<int> {
|
||||
if (const auto nextType = data.vnext_type()) {
|
||||
if (nextType->type() == mtpc_auth_codeTypeCall) {
|
||||
return data.vtimeout().value_or(60);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}();
|
||||
auto box = Box<Ui::ConfirmPhoneBox>(
|
||||
phone,
|
||||
sentCodeLength,
|
||||
timeout);
|
||||
const auto boxWeak = Ui::MakeWeak(box.data());
|
||||
box->resendRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
_api.request(MTPauth_ResendCode(
|
||||
MTP_string(phone),
|
||||
MTP_string(phoneHash)
|
||||
)).done([=](const MTPauth_SentCode &result) {
|
||||
if (boxWeak) {
|
||||
boxWeak->callDone();
|
||||
}
|
||||
}).send();
|
||||
}, box->lifetime());
|
||||
box->checkRequests(
|
||||
) | rpl::start_with_next([=](const QString &code) {
|
||||
if (_checkRequestId) {
|
||||
return;
|
||||
}
|
||||
_checkRequestId = _api.request(MTPaccount_ConfirmPhone(
|
||||
MTP_string(phoneHash),
|
||||
MTP_string(code)
|
||||
)).done([=](const MTPBool &result) {
|
||||
_checkRequestId = 0;
|
||||
controller->show(
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_confirm_phone_success(
|
||||
tr::now,
|
||||
lt_phone,
|
||||
Ui::FormatPhone(phone))),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_checkRequestId = 0;
|
||||
if (!boxWeak) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto errorText = MTP::IsFloodError(error)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: (error.type() == (u"PHONE_CODE_EMPTY"_q)
|
||||
|| error.type() == (u"PHONE_CODE_INVALID"_q))
|
||||
? tr::lng_bad_code(tr::now)
|
||||
: Lang::Hard::ServerError();
|
||||
boxWeak->showServerError(errorText);
|
||||
}).handleFloodErrors().send();
|
||||
}, box->lifetime());
|
||||
|
||||
controller->show(std::move(box), Ui::LayerOption::CloseOther);
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_sendRequestId = 0;
|
||||
_checkRequestId = 0;
|
||||
|
||||
const auto errorText = MTP::IsFloodError(error)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: (error.code() == 400)
|
||||
? tr::lng_confirm_phone_link_invalid(tr::now)
|
||||
: Lang::Hard::ServerError();
|
||||
controller->show(
|
||||
Box<Ui::InformBox>(errorText),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
36
Telegram/SourceFiles/api/api_confirm_phone.h
Normal file
36
Telegram/SourceFiles/api/api_confirm_phone.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ConfirmPhone final {
|
||||
public:
|
||||
explicit ConfirmPhone(not_null<ApiWrap*> api);
|
||||
|
||||
void resolve(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &phone,
|
||||
const QString &hash);
|
||||
|
||||
private:
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _sendRequestId = 0;
|
||||
mtpRequestId _checkRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_media.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
@@ -149,7 +149,8 @@ void EditMessageWithUploadedMedia(
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
if (mediaInvalid) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_edit_media_invalid_file(tr::now)),
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_edit_media_invalid_file(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -69,17 +70,28 @@ InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
|
||||
void InviteLinks::create(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit) {
|
||||
performCreate(peer, std::move(done), false, expireDate, usageLimit);
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
performCreate(
|
||||
peer,
|
||||
std::move(done),
|
||||
false,
|
||||
label,
|
||||
expireDate,
|
||||
usageLimit,
|
||||
requestApproval);
|
||||
}
|
||||
|
||||
void InviteLinks::performCreate(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
bool revokeLegacyPermanent,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit) {
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
if (const auto i = _createCallbacks.find(peer)
|
||||
; i != end(_createCallbacks)) {
|
||||
if (done) {
|
||||
@@ -97,11 +109,16 @@ void InviteLinks::performCreate(
|
||||
MTP_flags((revokeLegacyPermanent
|
||||
? Flag::f_legacy_revoke_permanent
|
||||
: Flag(0))
|
||||
| (!label.isEmpty() ? Flag::f_title : Flag(0))
|
||||
| (expireDate ? Flag::f_expire_date : Flag(0))
|
||||
| (usageLimit ? Flag::f_usage_limit : Flag(0))),
|
||||
| ((!requestApproval && usageLimit)
|
||||
? Flag::f_usage_limit
|
||||
: Flag(0))
|
||||
| (requestApproval ? Flag::f_request_needed : Flag(0))),
|
||||
peer->input,
|
||||
MTP_int(expireDate),
|
||||
MTP_int(usageLimit)
|
||||
MTP_int(usageLimit),
|
||||
MTP_string(label)
|
||||
)).done([=](const MTPExportedChatInvite &result) {
|
||||
const auto callbacks = _createCallbacks.take(peer);
|
||||
const auto link = prepend(peer, peer->session().user(), result);
|
||||
@@ -199,8 +216,10 @@ void InviteLinks::edit(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit,
|
||||
bool requestApproval,
|
||||
Fn<void(Link)> done) {
|
||||
performEdit(
|
||||
peer,
|
||||
@@ -208,8 +227,10 @@ void InviteLinks::edit(
|
||||
link,
|
||||
std::move(done),
|
||||
false,
|
||||
label,
|
||||
expireDate,
|
||||
usageLimit);
|
||||
usageLimit,
|
||||
requestApproval);
|
||||
}
|
||||
|
||||
void InviteLinks::performEdit(
|
||||
@@ -218,8 +239,10 @@ void InviteLinks::performEdit(
|
||||
const QString &link,
|
||||
Fn<void(Link)> done,
|
||||
bool revoke,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit) {
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
const auto key = LinkKey{ peer, link };
|
||||
if (_deleteCallbacks.contains(key)) {
|
||||
return;
|
||||
@@ -236,14 +259,21 @@ void InviteLinks::performEdit(
|
||||
callbacks.push_back(std::move(done));
|
||||
}
|
||||
using Flag = MTPmessages_EditExportedChatInvite::Flag;
|
||||
const auto flags = (revoke ? Flag::f_revoked : Flag(0))
|
||||
| (!revoke ? Flag::f_title : Flag(0))
|
||||
| (!revoke ? Flag::f_expire_date : Flag(0))
|
||||
| ((!revoke && !requestApproval) ? Flag::f_usage_limit : Flag(0))
|
||||
| ((!revoke && (requestApproval || !usageLimit))
|
||||
? Flag::f_request_needed
|
||||
: Flag(0));
|
||||
_api->request(MTPmessages_EditExportedChatInvite(
|
||||
MTP_flags((revoke ? Flag::f_revoked : Flag(0))
|
||||
| (!revoke ? Flag::f_expire_date : Flag(0))
|
||||
| (!revoke ? Flag::f_usage_limit : Flag(0))),
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_string(link),
|
||||
MTP_int(expireDate),
|
||||
MTP_int(usageLimit)
|
||||
MTP_int(usageLimit),
|
||||
MTP_bool(requestApproval),
|
||||
MTP_string(label)
|
||||
)).done([=](const MTPmessages_ExportedChatInvite &result) {
|
||||
const auto callbacks = _editCallbacks.take(key);
|
||||
const auto peer = key.peer;
|
||||
@@ -421,6 +451,88 @@ void InviteLinks::requestMyLinks(not_null<PeerData*> peer) {
|
||||
_firstSliceRequests.emplace(peer, requestId);
|
||||
}
|
||||
|
||||
void InviteLinks::processRequest(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
not_null<UserData*> user,
|
||||
bool approved,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
if (_processRequests.contains({ peer, user })) {
|
||||
return;
|
||||
}
|
||||
_processRequests.emplace(
|
||||
std::pair{ peer, user },
|
||||
ProcessRequest{ std::move(done), std::move(fail) });
|
||||
using Flag = MTPmessages_HideChatJoinRequest::Flag;
|
||||
_api->request(MTPmessages_HideChatJoinRequest(
|
||||
MTP_flags(approved ? Flag::f_approved : Flag(0)),
|
||||
peer->input,
|
||||
user->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (const auto chat = peer->asChat()) {
|
||||
if (chat->count > 0) {
|
||||
if (chat->participants.size() >= chat->count) {
|
||||
chat->participants.emplace(user);
|
||||
}
|
||||
++chat->count;
|
||||
}
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
_api->requestParticipantsCountDelayed(channel);
|
||||
}
|
||||
_api->applyUpdates(result);
|
||||
if (link.isEmpty() && approved) {
|
||||
// We don't know the link that was used for this user.
|
||||
// Prune all the cache.
|
||||
for (auto i = begin(_firstJoined); i != end(_firstJoined);) {
|
||||
if (i->first.peer == peer) {
|
||||
i = _firstJoined.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
_firstSlices.remove(peer);
|
||||
} else if (approved) {
|
||||
const auto i = _firstJoined.find({ peer, link });
|
||||
if (i != end(_firstJoined)) {
|
||||
++i->second.count;
|
||||
i->second.users.insert(
|
||||
begin(i->second.users),
|
||||
JoinedByLinkUser{ user, base::unixtime::now() });
|
||||
}
|
||||
}
|
||||
if (const auto callbacks = _processRequests.take({ peer, user })) {
|
||||
if (const auto &done = callbacks->done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto callbacks = _processRequests.take({ peer, user })) {
|
||||
if (const auto &fail = callbacks->fail) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void InviteLinks::applyExternalUpdate(
|
||||
not_null<PeerData*> peer,
|
||||
InviteLink updated) {
|
||||
if (const auto i = _firstSlices.find(peer); i != end(_firstSlices)) {
|
||||
for (auto &link : i->second.links) {
|
||||
if (link.link == updated.link) {
|
||||
link = updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
_updates.fire({
|
||||
.peer = peer,
|
||||
.admin = updated.admin,
|
||||
.was = updated.link,
|
||||
.now = updated,
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<JoinedByLinkSlice> InviteLinks::lookupJoinedFirstSlice(
|
||||
LinkKey key) const {
|
||||
const auto i = _firstJoined.find(key);
|
||||
@@ -484,8 +596,10 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(MTPmessages_GetChatInviteImporters::Flag::f_link),
|
||||
key.peer->input,
|
||||
MTP_string(key.link),
|
||||
MTPstring(), // q
|
||||
MTP_int(0), // offset_date
|
||||
MTP_inputUserEmpty(), // offset_user
|
||||
MTP_int(kJoinedFirstPage)
|
||||
@@ -623,12 +737,15 @@ auto InviteLinks::parse(
|
||||
return invite.match([&](const MTPDchatInviteExported &data) {
|
||||
return Link{
|
||||
.link = qs(data.vlink()),
|
||||
.label = qs(data.vtitle().value_or_empty()),
|
||||
.admin = peer->session().data().user(data.vadmin_id()),
|
||||
.date = data.vdate().v,
|
||||
.startDate = data.vstart_date().value_or_empty(),
|
||||
.expireDate = data.vexpire_date().value_or_empty(),
|
||||
.usageLimit = data.vusage_limit().value_or_empty(),
|
||||
.usage = data.vusage().value_or_empty(),
|
||||
.requested = data.vrequested().value_or_empty(),
|
||||
.requestApproval = data.is_request_needed(),
|
||||
.permanent = data.is_permanent(),
|
||||
.revoked = data.is_revoked(),
|
||||
};
|
||||
|
||||
@@ -13,12 +13,15 @@ namespace Api {
|
||||
|
||||
struct InviteLink {
|
||||
QString link;
|
||||
QString label;
|
||||
not_null<UserData*> admin;
|
||||
TimeId date = 0;
|
||||
TimeId startDate = 0;
|
||||
TimeId expireDate = 0;
|
||||
int usageLimit = 0;
|
||||
int usage = 0;
|
||||
int requested = 0;
|
||||
bool requestApproval = false;
|
||||
bool permanent = false;
|
||||
bool revoked = false;
|
||||
};
|
||||
@@ -60,14 +63,18 @@ public:
|
||||
void create(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done = nullptr,
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0);
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
void edit(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit,
|
||||
bool requestApproval,
|
||||
Fn<void(Link)> done = nullptr);
|
||||
void revoke(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -97,6 +104,15 @@ public:
|
||||
void requestMyLinks(not_null<PeerData*> peer);
|
||||
[[nodiscard]] const Links &myLinks(not_null<PeerData*> peer) const;
|
||||
|
||||
void processRequest(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
not_null<UserData*> user,
|
||||
bool approved,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail);
|
||||
void applyExternalUpdate(not_null<PeerData*> peer, InviteLink updated);
|
||||
|
||||
[[nodiscard]] rpl::producer<JoinedByLinkSlice> joinedFirstSliceValue(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
@@ -133,6 +149,10 @@ private:
|
||||
return (a.peer == b.peer) && (a.link == b.link);
|
||||
}
|
||||
};
|
||||
struct ProcessRequest {
|
||||
Fn<void()> done;
|
||||
Fn<void()> fail;
|
||||
};
|
||||
|
||||
[[nodiscard]] Links parseSlice(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -163,14 +183,18 @@ private:
|
||||
const QString &link,
|
||||
Fn<void(Link)> done,
|
||||
bool revoke,
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0);
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
void performCreate(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
bool revokeLegacyPermanent,
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0);
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
|
||||
void requestJoinedFirstSlice(LinkKey key);
|
||||
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(
|
||||
@@ -194,6 +218,10 @@ private:
|
||||
not_null<PeerData*>,
|
||||
std::vector<Fn<void()>>> _deleteRevokedCallbacks;
|
||||
|
||||
base::flat_map<
|
||||
std::pair<not_null<PeerData*>, not_null<UserData*>>,
|
||||
ProcessRequest> _processRequests;
|
||||
|
||||
rpl::event_stream<Update> _updates;
|
||||
|
||||
struct AllRevokedDestroyed {
|
||||
|
||||
212
Telegram/SourceFiles/api/api_peer_photo.cpp
Normal file
212
Telegram/SourceFiles/api/api_peer_photo.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_peer_photo.h"
|
||||
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
SendMediaReady PreparePeerPhoto(
|
||||
MTP::DcId dcId,
|
||||
PeerId peerId,
|
||||
QImage &&image) {
|
||||
PreparedPhotoThumbs photoThumbs;
|
||||
QVector<MTPPhotoSize> photoSizes;
|
||||
|
||||
QByteArray jpeg;
|
||||
QBuffer jpegBuffer(&jpeg);
|
||||
image.save(&jpegBuffer, "JPG", 87);
|
||||
|
||||
const auto scaled = [&](int size) {
|
||||
return image.scaled(
|
||||
size,
|
||||
size,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
};
|
||||
const auto push = [&](
|
||||
const char *type,
|
||||
QImage &&image,
|
||||
QByteArray bytes = QByteArray()) {
|
||||
photoSizes.push_back(MTP_photoSize(
|
||||
MTP_string(type),
|
||||
MTP_int(image.width()),
|
||||
MTP_int(image.height()), MTP_int(0)));
|
||||
photoThumbs.emplace(type[0], PreparedPhotoThumb{
|
||||
.image = std::move(image),
|
||||
.bytes = std::move(bytes)
|
||||
});
|
||||
};
|
||||
push("a", scaled(160));
|
||||
push("b", scaled(320));
|
||||
push("c", std::move(image), jpeg);
|
||||
|
||||
const auto id = base::RandomValue<PhotoId>();
|
||||
const auto photo = MTP_photo(
|
||||
MTP_flags(0),
|
||||
MTP_long(id),
|
||||
MTP_long(0),
|
||||
MTP_bytes(),
|
||||
MTP_int(base::unixtime::now()),
|
||||
MTP_vector<MTPPhotoSize>(photoSizes),
|
||||
MTPVector<MTPVideoSize>(),
|
||||
MTP_int(dcId));
|
||||
|
||||
QString file, filename;
|
||||
int32 filesize = 0;
|
||||
QByteArray data;
|
||||
|
||||
return SendMediaReady(
|
||||
SendMediaType::Photo,
|
||||
file,
|
||||
filename,
|
||||
filesize,
|
||||
data,
|
||||
id,
|
||||
id,
|
||||
u"jpg"_q,
|
||||
peerId,
|
||||
photo,
|
||||
photoThumbs,
|
||||
MTP_documentEmpty(MTP_long(0)),
|
||||
jpeg,
|
||||
0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PeerPhoto::PeerPhoto(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
crl::on_main(_session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
_session->uploader().photoReady(
|
||||
) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
|
||||
ready(data.fullId, data.file);
|
||||
}, _session->lifetime());
|
||||
});
|
||||
}
|
||||
|
||||
void PeerPhoto::upload(not_null<PeerData*> peer, QImage &&image) {
|
||||
peer = peer->migrateToOrMe();
|
||||
const auto ready = PreparePeerPhoto(
|
||||
_api.instance().mainDcId(),
|
||||
peer->id,
|
||||
std::move(image));
|
||||
|
||||
const auto fakeId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto already = ranges::find(
|
||||
_uploads,
|
||||
peer,
|
||||
[](const auto &pair) { return pair.second; });
|
||||
if (already != end(_uploads)) {
|
||||
_session->uploader().cancel(already->first);
|
||||
_uploads.erase(already);
|
||||
}
|
||||
_uploads.emplace(fakeId, peer);
|
||||
_session->uploader().uploadMedia(fakeId, ready);
|
||||
}
|
||||
|
||||
void PeerPhoto::clear(not_null<PhotoData*> photo) {
|
||||
const auto self = _session->user();
|
||||
if (self->userpicPhotoId() == photo->id) {
|
||||
_api.request(MTPphotos_UpdateProfilePhoto(
|
||||
MTP_inputPhotoEmpty()
|
||||
)).done([=](const MTPphotos_Photo &result) {
|
||||
self->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
}).send();
|
||||
} else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) {
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
_session->updates().applyUpdates(result);
|
||||
};
|
||||
if (const auto chat = photo->peer->asChat()) {
|
||||
_api.request(MTPmessages_EditChatPhoto(
|
||||
chat->inputChat,
|
||||
MTP_inputChatPhotoEmpty()
|
||||
)).done(applier).send();
|
||||
} else if (const auto channel = photo->peer->asChannel()) {
|
||||
_api.request(MTPchannels_EditPhoto(
|
||||
channel->inputChannel,
|
||||
MTP_inputChatPhotoEmpty()
|
||||
)).done(applier).send();
|
||||
}
|
||||
} else {
|
||||
_api.request(MTPphotos_DeletePhotos(
|
||||
MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
|
||||
)).send();
|
||||
_session->storage().remove(Storage::UserPhotosRemoveOne(
|
||||
peerToUser(self->id),
|
||||
photo->id));
|
||||
}
|
||||
}
|
||||
|
||||
void PeerPhoto::ready(const FullMsgId &msgId, const MTPInputFile &file) {
|
||||
const auto maybePeer = _uploads.take(msgId);
|
||||
if (!maybePeer) {
|
||||
return;
|
||||
}
|
||||
const auto peer = *maybePeer;
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
_session->updates().applyUpdates(result);
|
||||
};
|
||||
if (peer->isSelf()) {
|
||||
_api.request(MTPphotos_UploadProfilePhoto(
|
||||
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble() // video_start_ts
|
||||
)).done([=](const MTPphotos_Photo &result) {
|
||||
result.match([&](const MTPDphotos_photo &data) {
|
||||
_session->data().processPhoto(data.vphoto());
|
||||
_session->data().processUsers(data.vusers());
|
||||
});
|
||||
}).send();
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
const auto history = _session->data().history(chat);
|
||||
history->sendRequestId = _api.request(MTPmessages_EditChatPhoto(
|
||||
chat->inputChat,
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
const auto history = _session->data().history(channel);
|
||||
history->sendRequestId = _api.request(MTPchannels_EditPhoto(
|
||||
channel->inputChannel,
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
38
Telegram/SourceFiles/api/api_peer_photo.h
Normal file
38
Telegram/SourceFiles/api/api_peer_photo.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class PeerPhoto final {
|
||||
public:
|
||||
explicit PeerPhoto(not_null<ApiWrap*> api);
|
||||
|
||||
void upload(not_null<PeerData*> peer, QImage &&image);
|
||||
void clear(not_null<PhotoData*> photo);
|
||||
|
||||
private:
|
||||
void ready(const FullMsgId &msgId, const MTPInputFile &file);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<FullMsgId, not_null<PeerData*>> _uploads;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
201
Telegram/SourceFiles/api/api_polls.cpp
Normal file
201
Telegram/SourceFiles/api/api_polls.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_polls.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_message.h" // ShouldSendSilent
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
|
||||
return TimeId(msgId >> 32);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Polls::Polls(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void Polls::create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
Fn<void()> done,
|
||||
Fn<void(const MTP::Error &error)> fail) {
|
||||
_session->api().sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearLocalDraft();
|
||||
history->clearCloudDraft();
|
||||
history->startSavingCloudDraft();
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
if (action.options.scheduled) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto replyTo = action.replyTo;
|
||||
history->sendRequestId = _api.request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
PollDataToInputMedia(&data),
|
||||
MTP_string(),
|
||||
MTP_long(base::RandomValue<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](
|
||||
const MTPUpdates &result,
|
||||
const MTP::Response &response) mutable {
|
||||
_session->updates().applyUpdates(result);
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
_session->changes().historyUpdated(
|
||||
history,
|
||||
(action.options.scheduled
|
||||
? Data::HistoryUpdate::Flag::ScheduledSent
|
||||
: Data::HistoryUpdate::Flag::MessageSent));
|
||||
done();
|
||||
finish();
|
||||
}).fail([=](
|
||||
const MTP::Error &error,
|
||||
const MTP::Response &response) mutable {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
fail(error);
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
}
|
||||
|
||||
void Polls::sendVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options) {
|
||||
if (_pollVotesRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(itemId);
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto showSending = poll && !options.empty();
|
||||
const auto hideSending = [=] {
|
||||
if (showSending) {
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
poll->sendingVotes.clear();
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (showSending) {
|
||||
poll->sendingVotes = options;
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
|
||||
auto prepared = QVector<MTPbytes>();
|
||||
prepared.reserve(options.size());
|
||||
ranges::transform(
|
||||
options,
|
||||
ranges::back_inserter(prepared),
|
||||
[](const QByteArray &option) { return MTP_bytes(option); });
|
||||
const auto requestId = _api.request(MTPmessages_SendVote(
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_vector<MTPbytes>(prepared)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
_session->updates().applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
}).send();
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::close(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (_pollCloseRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
if (!poll) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_EditMessage(
|
||||
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTPstring(),
|
||||
PollDataToInputMedia(poll, true),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0) // schedule_date
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
}).send();
|
||||
_pollCloseRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::reloadResults(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (!IsServerMsgId(item->id)
|
||||
|| _pollReloadRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_GetPollResults(
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollReloadRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollReloadRequestIds.erase(itemId);
|
||||
}).send();
|
||||
_pollReloadRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
49
Telegram/SourceFiles/api/api_polls.h
Normal file
49
Telegram/SourceFiles/api/api_polls.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
class HistoryItem;
|
||||
struct PollData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct SendAction;
|
||||
|
||||
class Polls final {
|
||||
public:
|
||||
explicit Polls(not_null<ApiWrap*> api);
|
||||
|
||||
void create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
Fn<void()> done,
|
||||
Fn<void(const MTP::Error &error)> fail);
|
||||
void sendVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
void close(not_null<HistoryItem*> item);
|
||||
void reloadResults(not_null<HistoryItem*> item);
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -128,7 +128,7 @@ void SendExistingMedia(
|
||||
messagePostAuthor,
|
||||
media,
|
||||
caption,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -212,7 +212,7 @@ void SendExistingPhoto(
|
||||
}
|
||||
|
||||
bool SendDice(Api::MessageToSend &message) {
|
||||
const auto full = message.textWithTags.text.midRef(0).trimmed();
|
||||
const auto full = QStringView(message.textWithTags.text).trimmed();
|
||||
auto length = 0;
|
||||
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
|
||||
|| length != full.size()) {
|
||||
@@ -288,7 +288,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
messagePostAuthor,
|
||||
TextWithEntities(),
|
||||
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
@@ -335,7 +335,7 @@ void SendConfirmedFile(
|
||||
isEditing
|
||||
? file->to.replaceMediaOf
|
||||
: session->data().nextLocalMessageId());
|
||||
auto groupId = file->album ? file->album->groupId : uint64(0);
|
||||
const auto groupId = file->album ? file->album->groupId : uint64(0);
|
||||
if (file->album) {
|
||||
const auto proj = [](const SendingAlbum::Item &item) {
|
||||
return item.taskId;
|
||||
@@ -370,13 +370,6 @@ void SendConfirmedFile(
|
||||
session->user()).flags;
|
||||
TextUtilities::PrepareForSending(caption, prepareFlags);
|
||||
TextUtilities::Trim(caption);
|
||||
auto localEntities = Api::EntitiesToMTP(session, caption.entities);
|
||||
|
||||
if (itemToEdit) {
|
||||
if (const auto id = itemToEdit->groupId()) {
|
||||
groupId = id.value;
|
||||
}
|
||||
}
|
||||
|
||||
auto flags = isEditing ? MessageFlags() : NewMessageFlags(peer);
|
||||
if (file->to.replyTo) {
|
||||
@@ -408,7 +401,7 @@ void SendConfirmedFile(
|
||||
? session->user()->name
|
||||
: QString();
|
||||
|
||||
const auto media = [&] {
|
||||
const auto media = MTPMessageMedia([&] {
|
||||
if (file->type == SendMediaType::Photo) {
|
||||
return MTP_messageMediaPhoto(
|
||||
MTP_flags(MTPDmessageMediaPhoto::Flag::f_photo),
|
||||
@@ -427,38 +420,21 @@ void SendConfirmedFile(
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
}
|
||||
}();
|
||||
}());
|
||||
|
||||
if (itemToEdit) {
|
||||
itemToEdit->savePreviousMedia();
|
||||
itemToEdit->applyEdition(MTP_message(
|
||||
MTP_flags(MTPDmessage::Flag::f_media
|
||||
| ((flags & MessageFlag::HideEdited)
|
||||
? MTPDmessage::Flag::f_edit_hide
|
||||
: MTPDmessage::Flag())
|
||||
| (localEntities.v.isEmpty()
|
||||
? MTPDmessage::Flag()
|
||||
: MTPDmessage::Flag::f_entities)),
|
||||
MTP_int(newId.msg),
|
||||
peerToMTP(messageFromId),
|
||||
peerToMTP(file->to.peer),
|
||||
MTPMessageFwdHeader(),
|
||||
MTPlong(), // via_bot_id
|
||||
replyHeader,
|
||||
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
|
||||
MTP_string(caption.text),
|
||||
media,
|
||||
MTPReplyMarkup(),
|
||||
localEntities,
|
||||
MTPint(), // views
|
||||
MTPint(), // forwards
|
||||
MTPMessageReplies(),
|
||||
MTPint(), // edit_date
|
||||
MTP_string(messagePostAuthor),
|
||||
MTP_long(groupId),
|
||||
//MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTPint()).c_message());
|
||||
auto edition = HistoryMessageEdition();
|
||||
edition.isEditHide = (flags & MessageFlag::HideEdited);
|
||||
edition.editDate = 0;
|
||||
edition.views = 0;
|
||||
edition.forwards = 0;
|
||||
edition.ttl = 0;
|
||||
edition.mtpMedia = &media;
|
||||
edition.textWithEntities = caption;
|
||||
edition.useSameMarkup = true;
|
||||
edition.useSameReplies = true;
|
||||
itemToEdit->applyEdition(std::move(edition));
|
||||
} else {
|
||||
const auto viaBotId = UserId();
|
||||
history->addNewLocalMessage(
|
||||
@@ -471,7 +447,7 @@ void SendConfirmedFile(
|
||||
messagePostAuthor,
|
||||
caption,
|
||||
media,
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupId);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "app.h" // App::quitting
|
||||
@@ -239,11 +239,12 @@ Updates::Updates(not_null<Main::Session*> session)
|
||||
}).send();
|
||||
|
||||
using namespace rpl::mappers;
|
||||
base::ObservableViewer(
|
||||
api().fullPeerUpdated()
|
||||
) | rpl::filter([](not_null<PeerData*> peer) {
|
||||
return peer->isChat() || peer->isMegagroup();
|
||||
}) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
session->changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::filter([](const Data::PeerUpdate &update) {
|
||||
return update.peer->isChat() || update.peer->isMegagroup();
|
||||
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
const auto peer = update.peer;
|
||||
if (const auto list = _pendingSpeakingCallParticipants.take(peer)) {
|
||||
if (const auto call = peer->groupCall()) {
|
||||
for (const auto &[participantPeerId, when] : *list) {
|
||||
@@ -1965,6 +1966,19 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updatePendingJoinRequests: {
|
||||
const auto &d = update.c_updatePendingJoinRequests();
|
||||
if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) {
|
||||
const auto count = d.vrequests_pending().v;
|
||||
const auto &requesters = d.vrecent_requesters().v;
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setPendingRequestsCount(count, requesters);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setPendingRequestsCount(count, requesters);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateServiceNotification: {
|
||||
const auto &d = update.c_updateServiceNotification();
|
||||
const auto text = TextWithEntities {
|
||||
@@ -1976,7 +1990,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
} else if (d.is_popup()) {
|
||||
const auto &windows = session().windows();
|
||||
if (!windows.empty()) {
|
||||
windows.front()->window().show(Box<InformBox>(text));
|
||||
windows.front()->window().show(Box<Ui::InformBox>(text));
|
||||
}
|
||||
} else {
|
||||
session().data().serviceNotification(text, d.vmedia());
|
||||
@@ -2100,6 +2114,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
auto &d = update.c_updateChannel();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
channel->inviter = UserId(0);
|
||||
channel->inviteViaRequest = false;
|
||||
if (channel->amIn()) {
|
||||
if (channel->isMegagroup()
|
||||
&& !channel->amCreator()
|
||||
|
||||
136
Telegram/SourceFiles/api/api_views.cpp
Normal file
136
Telegram/SourceFiles/api/api_views.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_views.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
// Send channel views each second.
|
||||
constexpr auto kSendViewsTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance())
|
||||
, _incrementTimer([=] { viewsIncrement(); }) {
|
||||
}
|
||||
|
||||
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
|
||||
auto peer = item->history()->peer;
|
||||
auto i = _incremented.find(peer);
|
||||
if (i != _incremented.cend()) {
|
||||
if (i->second.contains(item->id)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
i = _incremented.emplace(peer).first;
|
||||
}
|
||||
i->second.emplace(item->id);
|
||||
auto j = _toIncrement.find(peer);
|
||||
if (j == _toIncrement.cend()) {
|
||||
j = _toIncrement.emplace(peer).first;
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
j->second.emplace(item->id);
|
||||
}
|
||||
|
||||
void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::viewsIncrement() {
|
||||
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
|
||||
if (_incrementRequests.contains(i->first)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<MTPint> ids;
|
||||
ids.reserve(i->second.size());
|
||||
for (const auto &msgId : i->second) {
|
||||
ids.push_back(MTP_int(msgId));
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
|
||||
i->first->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_bool(true)
|
||||
)).done([=](
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
done(ids, result, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
fail(error, requestId);
|
||||
}).afterDelay(5).send();
|
||||
|
||||
_incrementRequests.emplace(i->first, requestId);
|
||||
i = _toIncrement.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
const auto &data = result.c_messages_messageViews();
|
||||
auto &owner = _session->data();
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
auto &v = data.vviews().v;
|
||||
if (ids.size() == v.size()) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id != requestId) {
|
||||
continue;
|
||||
}
|
||||
const auto channel = peerToChannel(peer->id);
|
||||
for (auto j = 0, l = int(ids.size()); j < l; ++j) {
|
||||
if (const auto item = owner.message(channel, ids[j].v)) {
|
||||
v[j].match([&](const MTPDmessageViews &data) {
|
||||
if (const auto views = data.vviews()) {
|
||||
item->setViewsCount(views->v);
|
||||
}
|
||||
if (const auto forwards = data.vforwards()) {
|
||||
item->setForwardsCount(forwards->v);
|
||||
}
|
||||
if (const auto replies = data.vreplies()) {
|
||||
item->setReplies(
|
||||
HistoryMessageRepliesData(replies));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id == requestId) {
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
49
Telegram/SourceFiles/api/api_views.h
Normal file
49
Telegram/SourceFiles/api/api_views.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ViewsManager final {
|
||||
public:
|
||||
explicit ViewsManager(not_null<ApiWrap*> api);
|
||||
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
void viewsIncrement();
|
||||
|
||||
void done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId);
|
||||
void fail(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _incremented;
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _toIncrement;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _incrementRequests;
|
||||
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
|
||||
base::Timer _incrementTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -159,7 +159,7 @@ struct State {
|
||||
auto &entry = context->cache(item);
|
||||
entry.requestId = 0;
|
||||
auto peers = std::vector<PeerId>();
|
||||
peers.reserve(std::max(result.v.size(), 1));
|
||||
peers.reserve(std::max(int(result.v.size()), 1));
|
||||
for (const auto &id : result.v) {
|
||||
peers.push_back(UserId(id));
|
||||
}
|
||||
@@ -355,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
} else if (UpdateUserpics(state, item, peers)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_hash.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "api/api_media.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_polls.h"
|
||||
#include "api/api_sending.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_self_destruct.h"
|
||||
@@ -21,12 +23,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_views.h"
|
||||
#include "api/api_confirm_phone.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
@@ -60,7 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "main/main_account.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "window/notifications_manager.h"
|
||||
@@ -71,6 +74,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "support/support_helper.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/download_manager_mtproto.h"
|
||||
@@ -111,6 +115,7 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000);
|
||||
constexpr auto kNotifySettingSaveTimeout = crl::time(1000);
|
||||
constexpr auto kDialogsFirstLoad = 20;
|
||||
constexpr auto kDialogsPerPage = 500;
|
||||
constexpr auto kJoinErrorDuration = 5 * crl::time(1000);
|
||||
|
||||
using PhotoFileLocationId = Data::PhotoFileLocationId;
|
||||
using DocumentFileLocationId = Data::DocumentFileLocationId;
|
||||
@@ -141,15 +146,14 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
|
||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
||||
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
||||
, _views(std::make_unique<Api::ViewsManager>(this))
|
||||
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
|
||||
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
|
||||
, _polls(std::make_unique<Api::Polls>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
_session->uploader().photoReady(
|
||||
) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
|
||||
photoUploadReady(data.fullId, data.file);
|
||||
}, _session->lifetime());
|
||||
|
||||
_session->data().chatsFilters().changed(
|
||||
) | rpl::filter([=] {
|
||||
return _session->data().chatsFilters().archiveNeeded();
|
||||
@@ -352,7 +356,7 @@ void ApiWrap::checkChatInvite(
|
||||
)).done(std::move(done)).fail(std::move(fail)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::importChatInvite(const QString &hash) {
|
||||
void ApiWrap::importChatInvite(const QString &hash, bool isGroup) {
|
||||
request(MTPmessages_ImportChatInvite(
|
||||
MTP_string(hash)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
@@ -389,13 +393,20 @@ void ApiWrap::importChatInvite(const QString &hash) {
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
if (type == qstr("CHANNELS_TOO_MUCH")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_join_channel_error(tr::now)));
|
||||
} else if (error.code() == 400) {
|
||||
Ui::show(Box<InformBox>((type == qstr("USERS_TOO_MUCH"))
|
||||
? tr::lng_group_invite_no_room(tr::now)
|
||||
: tr::lng_group_invite_bad_link(tr::now)));
|
||||
}
|
||||
Ui::hideLayer();
|
||||
Ui::ShowMultilineToast({ .text = { [&] {
|
||||
if (type == qstr("INVITE_REQUEST_SENT")) {
|
||||
return isGroup
|
||||
? tr::lng_group_request_sent(tr::now)
|
||||
: tr::lng_group_request_sent_channel(tr::now);
|
||||
} else if (type == qstr("CHANNELS_TOO_MUCH")) {
|
||||
return tr::lng_join_channel_error(tr::now);
|
||||
} else if (type == qstr("USERS_TOO_MUCH")) {
|
||||
return tr::lng_group_invite_no_room(tr::now);
|
||||
} else {
|
||||
return tr::lng_group_invite_bad_link(tr::now);
|
||||
}
|
||||
}() }, .duration = kJoinErrorDuration });
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -464,19 +475,19 @@ void ApiWrap::sendMessageFail(
|
||||
uint64 randomId,
|
||||
FullMsgId itemId) {
|
||||
if (error.type() == qstr("PEER_FLOOD")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
PeerFloodErrorText(&session(), PeerFloodType::Send)));
|
||||
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
const auto link = textcmdLink(
|
||||
session().createInternalLinkFull(qsl("spambot")),
|
||||
tr::lng_cant_more_info(tr::now));
|
||||
Ui::show(Box<InformBox>(tr::lng_error_public_groups_denied(
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_error_public_groups_denied(
|
||||
tr::now,
|
||||
lt_more_info,
|
||||
link)));
|
||||
} else if (error.type().startsWith(qstr("SLOWMODE_WAIT_"))) {
|
||||
const auto chop = qstr("SLOWMODE_WAIT_").size();
|
||||
const auto left = error.type().midRef(chop).toInt();
|
||||
const auto left = base::StringViewMid(error.type(), chop).toInt();
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
const auto seconds = channel->slowmodeSeconds();
|
||||
if (seconds >= left) {
|
||||
@@ -491,7 +502,7 @@ void ApiWrap::sendMessageFail(
|
||||
Assert(peer->isUser());
|
||||
if (const auto item = scheduled.lookupItem(peer->id, itemId.msg)) {
|
||||
scheduled.removeSending(item);
|
||||
Ui::show(Box<InformBox>(tr::lng_cant_do_this(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_cant_do_this(tr::now)));
|
||||
}
|
||||
}
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
@@ -558,7 +569,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(nullptr, result, requestId);
|
||||
_session->data().processExistingMessages(nullptr, result);
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -584,7 +596,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(channel, result, requestId);
|
||||
_session->data().processExistingMessages(channel, result);
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -600,37 +613,6 @@ void ApiWrap::resolveMessageDatas() {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
|
||||
const auto handleResult = [&](auto &&result) {
|
||||
_session->data().processUsers(result.vusers());
|
||||
_session->data().processChats(result.vchats());
|
||||
_session->data().processMessages(
|
||||
result.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
};
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages:
|
||||
handleResult(msgs.c_messages_messages());
|
||||
break;
|
||||
case mtpc_messages_messagesSlice:
|
||||
handleResult(msgs.c_messages_messagesSlice());
|
||||
break;
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
|
||||
}
|
||||
handleResult(d);
|
||||
} break;
|
||||
case mtpc_messages_messagesNotModified:
|
||||
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
|
||||
break;
|
||||
}
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId) {
|
||||
@@ -662,8 +644,8 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto fallback = [&] {
|
||||
auto linkChannel = channel;
|
||||
auto linkItemId = item->id;
|
||||
auto linkCommentId = 0;
|
||||
auto linkThreadId = 0;
|
||||
auto linkCommentId = MsgId();
|
||||
auto linkThreadId = MsgId();
|
||||
if (inRepliesContext) {
|
||||
if (const auto rootId = item->replyToTop()) {
|
||||
const auto root = item->history()->owner().message(
|
||||
@@ -693,11 +675,11 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto query = base
|
||||
+ '/'
|
||||
+ QString::number(linkItemId)
|
||||
+ QString::number(linkItemId.bare)
|
||||
+ (linkCommentId
|
||||
? "?comment=" + QString::number(linkCommentId)
|
||||
? "?comment=" + QString::number(linkCommentId.bare)
|
||||
: linkThreadId
|
||||
? "?thread=" + QString::number(linkThreadId)
|
||||
? "?thread=" + QString::number(linkThreadId.bare)
|
||||
: "");
|
||||
if (linkChannel->hasUsername()
|
||||
&& !linkChannel->isMegagroup()
|
||||
@@ -1123,7 +1105,9 @@ void ApiWrap::gotChatFull(
|
||||
_fullPeerRequests.erase(i);
|
||||
}
|
||||
}
|
||||
fullPeerUpdated().notify(peer);
|
||||
_session->changes().peerUpdated(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
void ApiWrap::gotUserFull(
|
||||
@@ -1146,7 +1130,9 @@ void ApiWrap::gotUserFull(
|
||||
_fullPeerRequests.erase(i);
|
||||
}
|
||||
}
|
||||
fullPeerUpdated().notify(user);
|
||||
_session->changes().peerUpdated(
|
||||
user,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
void ApiWrap::requestPeer(not_null<PeerData*> peer) {
|
||||
@@ -1278,7 +1264,7 @@ void ApiWrap::migrateDone(
|
||||
void ApiWrap::migrateFail(not_null<PeerData*> peer, const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
if (type == qstr("CHANNELS_TOO_MUCH")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_migrate_error(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_migrate_error(tr::now)));
|
||||
}
|
||||
if (auto handlers = _migrateCallbacks.take(peer)) {
|
||||
for (auto &handler : *handlers) {
|
||||
@@ -1571,7 +1557,9 @@ void ApiWrap::applyLastParticipantsList(
|
||||
(Data::PeerUpdate::Flag::Members | Data::PeerUpdate::Flag::Admins));
|
||||
|
||||
channel->mgInfo->botStatus = botStatus;
|
||||
fullPeerUpdated().notify(channel);
|
||||
_session->changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
void ApiWrap::applyBotsList(
|
||||
@@ -1620,7 +1608,9 @@ void ApiWrap::applyBotsList(
|
||||
}
|
||||
|
||||
channel->mgInfo->botStatus = botStatus;
|
||||
fullPeerUpdated().notify(channel);
|
||||
_session->changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
@@ -1628,9 +1618,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto finalize = [=](UserId inviter, TimeId inviteDate) {
|
||||
const auto finalize = [=](
|
||||
UserId inviter = -1,
|
||||
TimeId inviteDate = 0,
|
||||
bool inviteViaRequest = false) {
|
||||
channel->inviter = inviter;
|
||||
channel->inviteDate = inviteDate;
|
||||
channel->inviteViaRequest = inviteViaRequest;
|
||||
if (const auto history = _session->data().historyLoaded(channel)) {
|
||||
if (history->lastMessageKnown()) {
|
||||
history->checkLocalMessages();
|
||||
@@ -1651,7 +1645,10 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
|
||||
const auto &participant = data.vparticipant();
|
||||
participant.match([&](const MTPDchannelParticipantSelf &data) {
|
||||
finalize(data.vinviter_id().v, data.vdate().v);
|
||||
finalize(
|
||||
data.vinviter_id().v,
|
||||
data.vdate().v,
|
||||
data.is_via_invite());
|
||||
}, [&](const MTPDchannelParticipantCreator &) {
|
||||
if (channel->mgInfo) {
|
||||
channel->mgInfo->creator = _session->user();
|
||||
@@ -1664,13 +1661,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
finalize(inviter, data.vdate().v);
|
||||
}, [&](const MTPDchannelParticipantBanned &data) {
|
||||
LOG(("API Error: Got self banned participant."));
|
||||
finalize(-1, 0);
|
||||
finalize();
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
LOG(("API Error: Got self regular participant."));
|
||||
finalize(-1, 0);
|
||||
finalize();
|
||||
}, [&](const MTPDchannelParticipantLeft &data) {
|
||||
LOG(("API Error: Got self left participant."));
|
||||
finalize(-1, 0);
|
||||
finalize();
|
||||
});
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
@@ -1678,7 +1675,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
if (error.type() == qstr("CHANNEL_PRIVATE")) {
|
||||
channel->privateErrorReceived();
|
||||
}
|
||||
finalize(-1, 0);
|
||||
finalize();
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
}
|
||||
|
||||
@@ -1759,7 +1756,7 @@ void ApiWrap::deleteAllFromUser(
|
||||
? history->collectMessagesFromUserToDelete(from)
|
||||
: QVector<MsgId>();
|
||||
const auto channelId = peerToChannel(channel->id);
|
||||
for (const auto msgId : ids) {
|
||||
for (const auto &msgId : ids) {
|
||||
if (const auto item = _session->data().message(channelId, msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
@@ -2105,19 +2102,35 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
||||
_channelAmInRequests.remove(channel);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == qstr("CHANNEL_PRIVATE")
|
||||
const auto &type = error.type();
|
||||
if (type == qstr("CHANNEL_PRIVATE")
|
||||
&& channel->invitePeekExpires()) {
|
||||
channel->privateErrorReceived();
|
||||
} else if (error.type() == qstr("CHANNEL_PRIVATE")
|
||||
|| error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA")
|
||||
|| error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
Ui::show(Box<InformBox>(channel->isMegagroup()
|
||||
? tr::lng_group_not_accessible(tr::now)
|
||||
: tr::lng_channel_not_accessible(tr::now)));
|
||||
} else if (error.type() == qstr("CHANNELS_TOO_MUCH")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_join_channel_error(tr::now)));
|
||||
} else if (error.type() == qstr("USERS_TOO_MUCH")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_group_full(tr::now)));
|
||||
} else {
|
||||
const auto text = [&] {
|
||||
if (type == qstr("INVITE_REQUEST_SENT")) {
|
||||
return channel->isMegagroup()
|
||||
? tr::lng_group_request_sent(tr::now)
|
||||
: tr::lng_group_request_sent_channel(tr::now);
|
||||
} else if (type == qstr("CHANNEL_PRIVATE")
|
||||
|| type == qstr("CHANNEL_PUBLIC_GROUP_NA")
|
||||
|| type == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
return channel->isMegagroup()
|
||||
? tr::lng_group_not_accessible(tr::now)
|
||||
: tr::lng_channel_not_accessible(tr::now);
|
||||
} else if (type == qstr("CHANNELS_TOO_MUCH")) {
|
||||
return tr::lng_join_channel_error(tr::now);
|
||||
} else if (type == qstr("USERS_TOO_MUCH")) {
|
||||
return tr::lng_group_full(tr::now);
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
if (!text.isEmpty()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { text },
|
||||
.duration = kJoinErrorDuration,
|
||||
});
|
||||
}
|
||||
}
|
||||
_channelAmInRequests.remove(channel);
|
||||
}).send();
|
||||
@@ -2220,7 +2233,7 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
|
||||
void ApiWrap::updatePrivacyLastSeens() {
|
||||
const auto now = base::unixtime::now();
|
||||
_session->data().enumerateUsers([&](UserData *user) {
|
||||
if (user->isSelf() || !user->isFullLoaded()) {
|
||||
if (user->isSelf() || !user->isLoaded()) {
|
||||
return;
|
||||
}
|
||||
if (user->onlineTill <= 0) {
|
||||
@@ -3289,7 +3302,7 @@ void ApiWrap::requestMessageAfterDate(
|
||||
// So we request a message with offset_date = desired_date - 1 and add_offset = -1.
|
||||
// This should give us the first message with date >= desired_date.
|
||||
const auto offsetId = 0;
|
||||
const auto offsetDate = static_cast<int>(base::QDateToDateTime(date).toTime_t()) - 1;
|
||||
const auto offsetDate = static_cast<int>(date.startOfDay().toSecsSinceEpoch()) - 1;
|
||||
const auto addOffset = -1;
|
||||
const auto limit = 1;
|
||||
const auto maxId = 0;
|
||||
@@ -3837,7 +3850,7 @@ void ApiWrap::sendSharedContact(
|
||||
MTP_string(lastName),
|
||||
MTP_string(), // vcard
|
||||
MTP_long(userId.bare)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
@@ -4101,7 +4114,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
messagePostAuthor,
|
||||
sending,
|
||||
media,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -4525,105 +4538,6 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
|
||||
action.replaceMediaOf);
|
||||
}
|
||||
|
||||
void ApiWrap::uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image) {
|
||||
peer = peer->migrateToOrMe();
|
||||
const auto ready = PreparePeerPhoto(
|
||||
instance().mainDcId(),
|
||||
peer->id,
|
||||
std::move(image));
|
||||
|
||||
const auto fakeId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto already = ranges::find(
|
||||
_peerPhotoUploads,
|
||||
peer,
|
||||
[](const auto &pair) { return pair.second; });
|
||||
if (already != end(_peerPhotoUploads)) {
|
||||
_session->uploader().cancel(already->first);
|
||||
_peerPhotoUploads.erase(already);
|
||||
}
|
||||
_peerPhotoUploads.emplace(fakeId, peer);
|
||||
_session->uploader().uploadMedia(fakeId, ready);
|
||||
}
|
||||
|
||||
void ApiWrap::photoUploadReady(
|
||||
const FullMsgId &msgId,
|
||||
const MTPInputFile &file) {
|
||||
if (const auto maybePeer = _peerPhotoUploads.take(msgId)) {
|
||||
const auto peer = *maybePeer;
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
};
|
||||
if (peer->isSelf()) {
|
||||
request(MTPphotos_UploadProfilePhoto(
|
||||
MTP_flags(MTPphotos_UploadProfilePhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble() // video_start_ts
|
||||
)).done([=](const MTPphotos_Photo &result) {
|
||||
result.match([&](const MTPDphotos_photo &data) {
|
||||
_session->data().processPhoto(data.vphoto());
|
||||
_session->data().processUsers(data.vusers());
|
||||
});
|
||||
}).send();
|
||||
} else if (const auto chat = peer->asChat()) {
|
||||
const auto history = _session->data().history(chat);
|
||||
history->sendRequestId = request(MTPmessages_EditChatPhoto(
|
||||
chat->inputChat,
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
const auto history = _session->data().history(channel);
|
||||
history->sendRequestId = request(MTPchannels_EditPhoto(
|
||||
channel->inputChannel,
|
||||
MTP_inputChatUploadedPhoto(
|
||||
MTP_flags(MTPDinputChatUploadedPhoto::Flag::f_file),
|
||||
file,
|
||||
MTPInputFile(), // video
|
||||
MTPdouble()) // video_start_ts
|
||||
)).done(applier).afterRequest(history->sendRequestId).send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::clearPeerPhoto(not_null<PhotoData*> photo) {
|
||||
const auto self = _session->user();
|
||||
if (self->userpicPhotoId() == photo->id) {
|
||||
request(MTPphotos_UpdateProfilePhoto(
|
||||
MTP_inputPhotoEmpty()
|
||||
)).done([=](const MTPphotos_Photo &result) {
|
||||
self->setPhoto(MTP_userProfilePhotoEmpty());
|
||||
}).send();
|
||||
} else if (photo->peer && photo->peer->userpicPhotoId() == photo->id) {
|
||||
const auto applier = [=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
};
|
||||
if (const auto chat = photo->peer->asChat()) {
|
||||
request(MTPmessages_EditChatPhoto(
|
||||
chat->inputChat,
|
||||
MTP_inputChatPhotoEmpty()
|
||||
)).done(applier).send();
|
||||
} else if (const auto channel = photo->peer->asChannel()) {
|
||||
request(MTPchannels_EditPhoto(
|
||||
channel->inputChannel,
|
||||
MTP_inputChatPhotoEmpty()
|
||||
)).done(applier).send();
|
||||
}
|
||||
} else {
|
||||
request(MTPphotos_DeletePhotos(
|
||||
MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
|
||||
)).send();
|
||||
_session->storage().remove(Storage::UserPhotosRemoveOne(
|
||||
peerToUser(self->id),
|
||||
photo->id));
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::reloadContactSignupSilent() {
|
||||
if (_contactSignupSilentRequestId) {
|
||||
return;
|
||||
@@ -4666,34 +4580,27 @@ void ApiWrap::saveContactSignupSilent(bool silent) {
|
||||
_contactSignupSilentRequestId = requestId;
|
||||
}
|
||||
|
||||
void ApiWrap::saveSelfBio(const QString &text, FnMut<void()> done) {
|
||||
if (_saveBioRequestId) {
|
||||
if (text != _saveBioText) {
|
||||
request(_saveBioRequestId).cancel();
|
||||
void ApiWrap::saveSelfBio(const QString &text) {
|
||||
if (_bio.requestId) {
|
||||
if (text != _bio.requestedText) {
|
||||
request(_bio.requestId).cancel();
|
||||
} else {
|
||||
if (done) {
|
||||
_saveBioDone = std::move(done);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
_saveBioText = text;
|
||||
_saveBioDone = std::move(done);
|
||||
_saveBioRequestId = request(MTPaccount_UpdateProfile(
|
||||
_bio.requestedText = text;
|
||||
_bio.requestId = request(MTPaccount_UpdateProfile(
|
||||
MTP_flags(MTPaccount_UpdateProfile::Flag::f_about),
|
||||
MTPstring(),
|
||||
MTPstring(),
|
||||
MTP_string(text)
|
||||
)).done([=](const MTPUser &result) {
|
||||
_saveBioRequestId = 0;
|
||||
_bio.requestId = 0;
|
||||
|
||||
_session->data().processUsers(MTP_vector<MTPUser>(1, result));
|
||||
_session->user()->setAbout(_saveBioText);
|
||||
if (_saveBioDone) {
|
||||
_saveBioDone();
|
||||
}
|
||||
_session->data().processUser(result);
|
||||
_session->user()->setAbout(_bio.requestedText);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_saveBioRequestId = 0;
|
||||
_bio.requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -4733,167 +4640,18 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
|
||||
return *_inviteLinks;
|
||||
}
|
||||
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
Fn<void()> done,
|
||||
Fn<void(const MTP::Error &error)> fail) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearLocalDraft();
|
||||
history->clearCloudDraft();
|
||||
history->startSavingCloudDraft();
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
if (action.options.scheduled) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto replyTo = action.replyTo;
|
||||
history->sendRequestId = request(MTPmessages_SendMedia(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
PollDataToInputMedia(&data),
|
||||
MTP_string(),
|
||||
MTP_long(base::RandomValue<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled)
|
||||
)).done([=](
|
||||
const MTPUpdates &result,
|
||||
const MTP::Response &response) mutable {
|
||||
applyUpdates(result);
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
_session->changes().historyUpdated(
|
||||
history,
|
||||
(action.options.scheduled
|
||||
? Data::HistoryUpdate::Flag::ScheduledSent
|
||||
: Data::HistoryUpdate::Flag::MessageSent));
|
||||
done();
|
||||
finish();
|
||||
}).fail([=](
|
||||
const MTP::Error &error,
|
||||
const MTP::Response &response) mutable {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
fail(error);
|
||||
finish();
|
||||
}).afterRequest(history->sendRequestId
|
||||
).send();
|
||||
return history->sendRequestId;
|
||||
});
|
||||
Api::ViewsManager &ApiWrap::views() {
|
||||
return *_views;
|
||||
}
|
||||
|
||||
void ApiWrap::sendPollVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options) {
|
||||
if (_pollVotesRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(itemId);
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto showSending = poll && !options.empty();
|
||||
const auto hideSending = [=] {
|
||||
if (showSending) {
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
poll->sendingVotes.clear();
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (showSending) {
|
||||
poll->sendingVotes = options;
|
||||
_session->data().requestItemRepaint(item);
|
||||
}
|
||||
|
||||
auto prepared = QVector<MTPbytes>();
|
||||
prepared.reserve(options.size());
|
||||
ranges::transform(
|
||||
options,
|
||||
ranges::back_inserter(prepared),
|
||||
[](const QByteArray &option) { return MTP_bytes(option); });
|
||||
const auto requestId = request(MTPmessages_SendVote(
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_vector<MTPbytes>(prepared)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
}).send();
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
Api::ConfirmPhone &ApiWrap::confirmPhone() {
|
||||
return *_confirmPhone;
|
||||
}
|
||||
|
||||
void ApiWrap::closePoll(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (_pollCloseRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
if (!poll) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = request(MTPmessages_EditMessage(
|
||||
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTPstring(),
|
||||
PollDataToInputMedia(poll, true),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0) // schedule_date
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
}).send();
|
||||
_pollCloseRequestIds.emplace(itemId, requestId);
|
||||
Api::PeerPhoto &ApiWrap::peerPhoto() {
|
||||
return *_peerPhoto;
|
||||
}
|
||||
|
||||
void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (!IsServerMsgId(item->id)
|
||||
|| _pollReloadRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = request(MTPmessages_GetPollResults(
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollReloadRequestIds.erase(itemId);
|
||||
applyUpdates(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollReloadRequestIds.erase(itemId);
|
||||
}).send();
|
||||
_pollReloadRequestIds.emplace(itemId, requestId);
|
||||
Api::Polls &ApiWrap::polls() {
|
||||
return *_polls;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ class SensitiveContent;
|
||||
class GlobalPrivacy;
|
||||
class UserPrivacy;
|
||||
class InviteLinks;
|
||||
class ViewsManager;
|
||||
class ConfirmPhone;
|
||||
class PeerPhoto;
|
||||
class Polls;
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -198,7 +202,7 @@ public:
|
||||
const QString &hash,
|
||||
FnMut<void(const MTPChatInvite &)> done,
|
||||
Fn<void(const MTP::Error &)> fail);
|
||||
void importChatInvite(const QString &hash);
|
||||
void importChatInvite(const QString &hash, bool isGroup);
|
||||
|
||||
void requestChannelMembersForAdd(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -266,10 +270,6 @@ public:
|
||||
void clearHistory(not_null<PeerData*> peer, bool revoke);
|
||||
void deleteConversation(not_null<PeerData*> peer, bool revoke);
|
||||
|
||||
base::Observable<PeerData*> &fullPeerUpdated() {
|
||||
return _fullPeerUpdated;
|
||||
}
|
||||
|
||||
bool isQuitPrevent();
|
||||
|
||||
void jumpToDate(Dialogs::Key chat, const QDate &date);
|
||||
@@ -381,15 +381,12 @@ public:
|
||||
uint64 randomId = 0,
|
||||
FullMsgId itemId = FullMsgId());
|
||||
|
||||
void uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image);
|
||||
void clearPeerPhoto(not_null<PhotoData*> photo);
|
||||
|
||||
void reloadContactSignupSilent();
|
||||
rpl::producer<bool> contactSignupSilent() const;
|
||||
std::optional<bool> contactSignupSilentCurrent() const;
|
||||
void saveContactSignupSilent(bool silent);
|
||||
|
||||
void saveSelfBio(const QString &text, FnMut<void()> done);
|
||||
void saveSelfBio(const QString &text);
|
||||
|
||||
[[nodiscard]] Api::Authorizations &authorizations();
|
||||
[[nodiscard]] Api::AttachedStickers &attachedStickers();
|
||||
@@ -400,17 +397,10 @@ public:
|
||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
||||
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
||||
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
Fn<void()> done,
|
||||
Fn<void(const MTP::Error &error)> fail);
|
||||
void sendPollVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
void closePoll(not_null<HistoryItem*> item);
|
||||
void reloadPollResults(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] Api::ViewsManager &views();
|
||||
[[nodiscard]] Api::ConfirmPhone &confirmPhone();
|
||||
[[nodiscard]] Api::PeerPhoto &peerPhoto();
|
||||
[[nodiscard]] Api::Polls &polls();
|
||||
|
||||
void updatePrivacyLastSeens();
|
||||
|
||||
@@ -456,7 +446,6 @@ private:
|
||||
void saveDraftsToCloud();
|
||||
|
||||
void resolveMessageDatas();
|
||||
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
|
||||
void finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId);
|
||||
@@ -573,8 +562,6 @@ private:
|
||||
FileReferencesHandler &&handler,
|
||||
Request &&data);
|
||||
|
||||
void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file);
|
||||
|
||||
void migrateDone(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<ChannelData*> channel);
|
||||
@@ -672,8 +659,6 @@ private:
|
||||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||
|
||||
base::Observable<PeerData*> _fullPeerUpdated;
|
||||
|
||||
mtpRequestId _topPromotionRequestId = 0;
|
||||
std::pair<QString, uint32> _topPromotionKey;
|
||||
TimeId _topPromotionNextRequestTime = TimeId(0);
|
||||
@@ -705,11 +690,10 @@ private:
|
||||
|
||||
std::vector<FnMut<void(const MTPUser &)>> _supportContactCallbacks;
|
||||
|
||||
base::flat_map<FullMsgId, not_null<PeerData*>> _peerPhotoUploads;
|
||||
|
||||
mtpRequestId _saveBioRequestId = 0;
|
||||
FnMut<void()> _saveBioDone;
|
||||
QString _saveBioText;
|
||||
struct {
|
||||
mtpRequestId requestId = 0;
|
||||
QString requestedText;
|
||||
} _bio;
|
||||
|
||||
const std::unique_ptr<Api::Authorizations> _authorizations;
|
||||
const std::unique_ptr<Api::AttachedStickers> _attachedStickers;
|
||||
@@ -720,10 +704,10 @@ private:
|
||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
||||
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
|
||||
const std::unique_ptr<Api::ViewsManager> _views;
|
||||
const std::unique_ptr<Api::ConfirmPhone> _confirmPhone;
|
||||
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
|
||||
const std::unique_ptr<Api::Polls> _polls;
|
||||
|
||||
mtpRequestId _wallPaperRequestId = 0;
|
||||
QString _wallPaperSlug;
|
||||
|
||||
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -104,7 +104,8 @@ void AboutBox::showVersionHistory() {
|
||||
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
|
||||
Ui::show(Box<InformBox>("The link to the current private alpha version of Telegram Desktop was copied to the clipboard."));
|
||||
Ui::show(Box<Ui::InformBox>("The link to the current private alpha "
|
||||
"version of Telegram Desktop was copied to the clipboard."));
|
||||
} else {
|
||||
UrlClickHandler::Open(Core::App().changelogLink());
|
||||
}
|
||||
|
||||
68
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
68
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/about_sponsored_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kUrl = "https://promote.telegram.org"_cs;
|
||||
|
||||
} // namespace
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_sponsored_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
|
||||
const auto addUrl = [&] {
|
||||
const auto &st = st::sponsoredUrlButton;
|
||||
const auto row = box->addRow(object_ptr<RpWidget>(box));
|
||||
row->resize(0, st.height + st.padding.top() + st.padding.bottom());
|
||||
const auto button = Ui::CreateChild<RoundButton>(
|
||||
row,
|
||||
rpl::single<QString>(kUrl.utf8()),
|
||||
st);
|
||||
button->setBrushOverride(Qt::NoBrush);
|
||||
button->setPenOverride(QPen(st::historyLinkInFg));
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
rpl::combine(
|
||||
row->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QSize &rowSize,
|
||||
const QSize &buttonSize) {
|
||||
button->moveToLeft(
|
||||
(rowSize.width() - buttonSize.width()) / 2,
|
||||
(rowSize.height() - buttonSize.height()) / 2);
|
||||
}, row->lifetime());
|
||||
button->addClickHandler([=] {
|
||||
QDesktopServices::openUrl({ kUrl.utf8() });
|
||||
});
|
||||
};
|
||||
|
||||
const auto &stLabel = st::aboutLabel;
|
||||
const auto info1 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info1->setText(tr::lng_sponsored_info_description1(tr::now));
|
||||
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
addUrl();
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
const auto info2 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info2->setText(tr::lng_sponsored_info_description2(tr::now));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
class ConfirmBox;
|
||||
class PeerListBox;
|
||||
|
||||
namespace Window {
|
||||
@@ -25,6 +22,7 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class ConfirmBox;
|
||||
class FlatLabel;
|
||||
class InputField;
|
||||
class PhoneInput;
|
||||
@@ -118,7 +116,10 @@ protected:
|
||||
|
||||
private:
|
||||
void createChannel(const QString &title, const QString &description);
|
||||
void createGroup(not_null<PeerListBox*> selectUsersBox, const QString &title, const std::vector<not_null<PeerData*>> &users);
|
||||
void createGroup(
|
||||
not_null<PeerListBox*> selectUsersBox,
|
||||
const QString &title,
|
||||
const std::vector<not_null<PeerData*>> &users);
|
||||
void submitName();
|
||||
void submit();
|
||||
void checkInviteLink();
|
||||
@@ -176,12 +177,10 @@ private:
|
||||
void check();
|
||||
void save();
|
||||
|
||||
void updateDone(const MTPBool &result);
|
||||
void updateFail(const MTP::Error &error);
|
||||
void updateFail(const QString &error);
|
||||
|
||||
void checkDone(const MTPBool &result);
|
||||
void checkFail(const MTP::Error &error);
|
||||
void firstCheckFail(const MTP::Error &error);
|
||||
void checkFail(const QString &error);
|
||||
void firstCheckFail(const QString &error);
|
||||
|
||||
void updateMaxHeight();
|
||||
|
||||
@@ -192,6 +191,7 @@ private:
|
||||
MTP::Sender _api;
|
||||
|
||||
bool _existing = false;
|
||||
bool _creatingInviteLink = false;
|
||||
|
||||
std::shared_ptr<Ui::RadioenumGroup<Privacy>> _privacyGroup;
|
||||
object_ptr<Ui::Radioenum<Privacy>> _public;
|
||||
@@ -209,7 +209,7 @@ private:
|
||||
mtpRequestId _checkRequestId = 0;
|
||||
QString _sentUsername, _checkUsername, _errorText, _goodText;
|
||||
|
||||
QTimer _checkTimer;
|
||||
base::Timer _checkTimer;
|
||||
|
||||
};
|
||||
|
||||
@@ -226,8 +226,7 @@ protected:
|
||||
private:
|
||||
void submit();
|
||||
void save();
|
||||
void saveSelfDone(const MTPUser &user);
|
||||
void saveSelfFail(const MTP::Error &error);
|
||||
void saveSelfFail(const QString &error);
|
||||
|
||||
const not_null<UserData*> _user;
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "styles/style_overview.h"
|
||||
@@ -176,7 +176,7 @@ void BackgroundBox::removePaper(const Data::WallPaper &paper) {
|
||||
)).send();
|
||||
};
|
||||
_controller->show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_background_sure_delete(tr::now),
|
||||
tr::lng_selected_delete(tr::now),
|
||||
tr::lng_cancel(tr::now),
|
||||
|
||||
@@ -28,7 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_resolver.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
|
||||
const auto frames = framesForStyle(st);
|
||||
auto &image = frames->image;
|
||||
const auto count = int(frames->ready.size());
|
||||
const auto index = int(std::round(toggled * (count - 1)));
|
||||
const auto index = int(base::SafeRound(toggled * (count - 1)));
|
||||
Assert(index >= 0 && index < count);
|
||||
if (!frames->ready[index]) {
|
||||
frames->ready[index] = true;
|
||||
@@ -288,7 +288,6 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
bool out) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
|
||||
const auto flags = MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| (out ? MessageFlag::Outgoing : MessageFlag(0));
|
||||
@@ -296,7 +295,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
const auto viaBotId = UserId();
|
||||
const auto groupedId = uint64();
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
history->nextNonHistoryEntryId(),
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
@@ -305,7 +304,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
QString(),
|
||||
TextWithEntities{ TextUtilities::Clean(text) },
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
@@ -785,7 +784,7 @@ bool BackgroundPreviewBox::Start(
|
||||
}
|
||||
if (!IsValidWallPaperSlug(slug)) {
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
return false;
|
||||
}
|
||||
controller->session().api().requestWallPaper(slug, crl::guard(controller, [=](
|
||||
@@ -795,7 +794,7 @@ bool BackgroundPreviewBox::Start(
|
||||
result.withUrlParams(params)));
|
||||
}), crl::guard(controller, [=](const MTP::Error &error) {
|
||||
controller->show(
|
||||
Box<InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_background_bad_link(tr::now)));
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,29 +82,40 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 24px;
|
||||
textFg: windowBoldFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
linkFont: font(16px semibold);
|
||||
linkFontOver: font(16px semibold underline);
|
||||
font: font(18px semibold);
|
||||
linkFont: font(18px semibold);
|
||||
linkFontOver: font(18px semibold underline);
|
||||
}
|
||||
}
|
||||
confirmInviteStatus: FlatLabel(boxLabel) {
|
||||
confirmInviteAbout: FlatLabel(boxLabel) {
|
||||
align: align(center);
|
||||
minWidth: 320px;
|
||||
textFg: windowSubTextFg;
|
||||
maxHeight: 60px;
|
||||
style: TextStyle(boxLabelStyle) {
|
||||
lineHeight: 19px;
|
||||
}
|
||||
}
|
||||
confirmInviteTitleTop: 106px;
|
||||
confirmInvitePhotoSize: 76px;
|
||||
confirmInvitePhotoTop: 20px;
|
||||
confirmInviteStatusTop: 136px;
|
||||
confirmInviteUserHeight: 84px;
|
||||
confirmInviteUserPhotoSize: 56px;
|
||||
confirmInviteUserPhotoTop: 166px;
|
||||
confirmInviteStatus: FlatLabel(confirmInviteAbout) {
|
||||
textFg: windowSubTextFg;
|
||||
style: boxLabelStyle;
|
||||
maxHeight: 0px;
|
||||
}
|
||||
confirmInviteAboutPadding: margins(36px, 4px, 36px, 10px);
|
||||
confirmInviteAboutRequestsPadding: margins(36px, 9px, 36px, 15px);
|
||||
confirmInviteTitleTop: 141px;
|
||||
confirmInvitePhotoSize: 96px;
|
||||
confirmInvitePhotoTop: 33px;
|
||||
confirmInviteStatusTop: 164px;
|
||||
confirmInviteUserHeight: 100px;
|
||||
confirmInviteUserPhotoSize: 50px;
|
||||
confirmInviteUserPhotoTop: 210px;
|
||||
confirmInviteUsersWidth: 320px;
|
||||
confirmInviteUserName: FlatLabel(defaultFlatLabel) {
|
||||
align: align(center);
|
||||
minWidth: 66px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
confirmInviteUserNameTop: 227px;
|
||||
confirmInviteUserNameTop: 264px;
|
||||
|
||||
confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 272px;
|
||||
@@ -363,7 +374,7 @@ aboutVersionLink: LinkButton(defaultLinkButton) {
|
||||
aboutTextTop: 34px;
|
||||
aboutSkip: 14px;
|
||||
aboutLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 330px;
|
||||
minWidth: 300px;
|
||||
align: align(topleft);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
lineHeight: 22px;
|
||||
@@ -973,3 +984,48 @@ autolockTimeField: InputField(scheduleTimeField) {
|
||||
heightMin: 20px;
|
||||
}
|
||||
autolockTimeWidth: 52px;
|
||||
|
||||
sponsoredUrlButtonSkip: 11px;
|
||||
sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
height: 32px;
|
||||
width: -42px;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
radius: roundRadiusLarge;
|
||||
padding: margins(2px, 2px, 2px, 2px);
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
requestsBoxItem: PeerListItem(peerListBoxItem) {
|
||||
height: 99px;
|
||||
button: OutlineButton(defaultPeerListButton) {
|
||||
textBg: contactsBg;
|
||||
textBgOver: contactsBg;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: contactsBgOver;
|
||||
}
|
||||
}
|
||||
}
|
||||
requestsBoxList: PeerList(peerListBox) {
|
||||
padding: margins(0px, 12px, 0px, 12px);
|
||||
item: requestsBoxItem;
|
||||
}
|
||||
requestsAcceptButton: RoundButton(defaultActiveButton) {
|
||||
width: -28px;
|
||||
height: 30px;
|
||||
textTop: 6px;
|
||||
}
|
||||
requestsRejectButton: RoundButton(defaultLightButton) {
|
||||
width: -28px;
|
||||
height: 30px;
|
||||
textTop: 6px;
|
||||
}
|
||||
requestAcceptPosition: point(71px, 58px);
|
||||
requestButtonsSkip: 9px;
|
||||
|
||||
@@ -9,26 +9,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/sent_code_field.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/special_fields.h"
|
||||
#include "boxes/confirm_phone_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/phone_banned_box.h"
|
||||
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "apiwrap.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void createErrorLabel(
|
||||
void CreateErrorLabel(
|
||||
QWidget *parent,
|
||||
object_ptr<Ui::FadeWrap<Ui::FlatLabel>> &label,
|
||||
const QString &text,
|
||||
@@ -67,7 +68,7 @@ void createErrorLabel(
|
||||
|
||||
class ChangePhoneBox::EnterPhone : public Ui::BoxContent {
|
||||
public:
|
||||
EnterPhone(QWidget*, not_null<Main::Session*> session);
|
||||
EnterPhone(QWidget*, not_null<Window::SessionController*> controller);
|
||||
|
||||
void setInnerFocus() override {
|
||||
_phone->setFocusFast();
|
||||
@@ -78,14 +79,16 @@ protected:
|
||||
|
||||
private:
|
||||
void submit();
|
||||
void sendPhoneDone(const MTPauth_SentCode &result, const QString &phoneNumber);
|
||||
void sendPhoneDone(
|
||||
const MTPauth_SentCode &result,
|
||||
const QString &phoneNumber);
|
||||
void sendPhoneFail(const MTP::Error &error, const QString &phoneNumber);
|
||||
void showError(const QString &text);
|
||||
void hideError() {
|
||||
showError(QString());
|
||||
}
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
object_ptr<Ui::PhoneInput> _phone = { nullptr };
|
||||
@@ -129,41 +132,51 @@ private:
|
||||
QString _hash;
|
||||
int _codeLength = 0;
|
||||
int _callTimeout = 0;
|
||||
object_ptr<SentCodeField> _code = { nullptr };
|
||||
object_ptr<Ui::SentCodeField> _code = { nullptr };
|
||||
object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _callLabel = { nullptr };
|
||||
mtpRequestId _requestId = 0;
|
||||
SentCodeCall _call;
|
||||
Ui::SentCodeCall _call;
|
||||
|
||||
};
|
||||
|
||||
ChangePhoneBox::EnterPhone::EnterPhone(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _api(&session->mtp()) {
|
||||
not_null<Window::SessionController*> controller)
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp()) {
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterPhone::prepare() {
|
||||
setTitle(tr::lng_change_phone_title());
|
||||
|
||||
auto phoneValue = QString();
|
||||
const auto phoneValue = QString();
|
||||
_phone.create(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
tr::lng_change_phone_new_title(),
|
||||
Countries::ExtractPhoneCode(_session->user()->phone()),
|
||||
Countries::ExtractPhoneCode(_controller->session().user()->phone()),
|
||||
phoneValue);
|
||||
|
||||
_phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height());
|
||||
_phone->resize(
|
||||
st::boxWidth - 2 * st::boxPadding.left(),
|
||||
_phone->height());
|
||||
_phone->moveToLeft(st::boxPadding.left(), st::boxLittleSkip);
|
||||
connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); });
|
||||
|
||||
auto description = object_ptr<Ui::FlatLabel>(this, tr::lng_change_phone_new_description(tr::now), st::changePhoneLabel);
|
||||
auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height;
|
||||
description->moveToLeft(st::boxPadding.left(), _phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
|
||||
const auto description = object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
tr::lng_change_phone_new_description(tr::now),
|
||||
st::changePhoneLabel);
|
||||
const auto errorSkip = st::boxLittleSkip
|
||||
+ st::changePhoneError.style.font->height;
|
||||
description->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
_phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
|
||||
|
||||
setDimensions(st::boxWidth, description->bottomNoMargins() + st::boxLittleSkip);
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
description->bottomNoMargins() + st::boxLittleSkip);
|
||||
|
||||
addButton(tr::lng_change_phone_new_submit(), [this] { submit(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
@@ -175,13 +188,15 @@ void ChangePhoneBox::EnterPhone::submit() {
|
||||
}
|
||||
hideError();
|
||||
|
||||
auto phoneNumber = _phone->getLastText().trimmed();
|
||||
const auto phoneNumber = _phone->getLastText().trimmed();
|
||||
_requestId = _api.request(MTPaccount_SendChangePhoneCode(
|
||||
MTP_string(phoneNumber),
|
||||
MTP_codeSettings(MTP_flags(0))
|
||||
)).done([=](const MTPauth_SentCode &result) {
|
||||
_requestId = 0;
|
||||
sendPhoneDone(result, phoneNumber);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
sendPhoneFail(error, phoneNumber);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
@@ -189,33 +204,45 @@ void ChangePhoneBox::EnterPhone::submit() {
|
||||
void ChangePhoneBox::EnterPhone::sendPhoneDone(
|
||||
const MTPauth_SentCode &result,
|
||||
const QString &phoneNumber) {
|
||||
Expects(result.type() == mtpc_auth_sentCode);
|
||||
_requestId = 0;
|
||||
using CodeData = const MTPDauth_sentCode&;
|
||||
const auto &data = result.match([](const auto &data) -> CodeData {
|
||||
return data;
|
||||
});
|
||||
|
||||
auto codeLength = 0;
|
||||
auto &data = result.c_auth_sentCode();
|
||||
switch (data.vtype().type()) {
|
||||
case mtpc_auth_sentCodeTypeApp:
|
||||
const auto hasLength = data.vtype().match([&](
|
||||
const MTPDauth_sentCodeTypeApp &typeData) {
|
||||
LOG(("Error: should not be in-app code!"));
|
||||
showError(Lang::Hard::ServerError());
|
||||
return;
|
||||
case mtpc_auth_sentCodeTypeSms: codeLength = data.vtype().c_auth_sentCodeTypeSms().vlength().v; break;
|
||||
case mtpc_auth_sentCodeTypeCall: codeLength = data.vtype().c_auth_sentCodeTypeCall().vlength().v; break;
|
||||
case mtpc_auth_sentCodeTypeFlashCall:
|
||||
return false;
|
||||
}, [&](const MTPDauth_sentCodeTypeSms &typeData) {
|
||||
codeLength = typeData.vlength().v;
|
||||
return true;
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &typeData) {
|
||||
codeLength = typeData.vlength().v;
|
||||
return true;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &typeData) {
|
||||
LOG(("Error: should not be flashcall!"));
|
||||
showError(Lang::Hard::ServerError());
|
||||
return false;
|
||||
});
|
||||
if (!hasLength) {
|
||||
return;
|
||||
}
|
||||
auto phoneCodeHash = qs(data.vphone_code_hash());
|
||||
auto callTimeout = 0;
|
||||
if (const auto nextType = data.vnext_type()) {
|
||||
if (nextType->type() == mtpc_auth_codeTypeCall) {
|
||||
callTimeout = data.vtimeout().value_or(60);
|
||||
const auto phoneCodeHash = qs(data.vphone_code_hash());
|
||||
const auto callTimeout = [&] {
|
||||
if (const auto nextType = data.vnext_type()) {
|
||||
return nextType->match([&](const MTPDauth_sentCodeTypeCall &) {
|
||||
return data.vtimeout().value_or(60);
|
||||
}, [](const auto &) {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
Ui::show(
|
||||
return 0;
|
||||
}();
|
||||
_controller->show(
|
||||
Box<EnterCode>(
|
||||
_session,
|
||||
&_controller->session(),
|
||||
phoneNumber,
|
||||
phoneCodeHash,
|
||||
codeLength,
|
||||
@@ -223,28 +250,36 @@ void ChangePhoneBox::EnterPhone::sendPhoneDone(
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterPhone::sendPhoneFail(const MTP::Error &error, const QString &phoneNumber) {
|
||||
_requestId = 0;
|
||||
void ChangePhoneBox::EnterPhone::sendPhoneFail(
|
||||
const MTP::Error &error,
|
||||
const QString &phoneNumber) {
|
||||
if (MTP::IsFloodError(error)) {
|
||||
showError(tr::lng_flood_error(tr::now));
|
||||
} else if (error.type() == qstr("PHONE_NUMBER_INVALID")) {
|
||||
showError(tr::lng_bad_phone(tr::now));
|
||||
} else if (error.type() == qstr("PHONE_NUMBER_BANNED")) {
|
||||
ShowPhoneBannedError(phoneNumber);
|
||||
Ui::ShowPhoneBannedError(&_controller->window(), phoneNumber);
|
||||
} else if (error.type() == qstr("PHONE_NUMBER_OCCUPIED")) {
|
||||
Ui::show(Box<InformBox>(
|
||||
tr::lng_change_phone_occupied(
|
||||
tr::now,
|
||||
lt_phone,
|
||||
Ui::FormatPhone(phoneNumber)),
|
||||
tr::lng_box_ok(tr::now)));
|
||||
_controller->show(
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_change_phone_occupied(
|
||||
tr::now,
|
||||
lt_phone,
|
||||
Ui::FormatPhone(phoneNumber)),
|
||||
tr::lng_box_ok(tr::now)),
|
||||
Ui::LayerOption::CloseOther);
|
||||
} else {
|
||||
showError(Lang::Hard::ServerError());
|
||||
}
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterPhone::showError(const QString &text) {
|
||||
createErrorLabel(this, _error, text, st::boxPadding.left(), _phone->y() + _phone->height() + st::boxLittleSkip);
|
||||
CreateErrorLabel(
|
||||
this,
|
||||
_error,
|
||||
text,
|
||||
st::boxPadding.left(),
|
||||
_phone->y() + _phone->height() + st::boxLittleSkip);
|
||||
if (!text.isEmpty()) {
|
||||
_phone->showError();
|
||||
}
|
||||
@@ -269,16 +304,23 @@ ChangePhoneBox::EnterCode::EnterCode(
|
||||
void ChangePhoneBox::EnterCode::prepare() {
|
||||
setTitle(tr::lng_change_phone_title());
|
||||
|
||||
auto descriptionText = tr::lng_change_phone_code_description(
|
||||
const auto descriptionText = tr::lng_change_phone_code_description(
|
||||
tr::now,
|
||||
lt_phone,
|
||||
Ui::Text::Bold(Ui::FormatPhone(_phone)),
|
||||
Ui::Text::WithEntities);
|
||||
auto description = object_ptr<Ui::FlatLabel>(this, rpl::single(descriptionText), st::changePhoneLabel);
|
||||
const auto description = object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
rpl::single(descriptionText),
|
||||
st::changePhoneLabel);
|
||||
description->moveToLeft(st::boxPadding.left(), 0);
|
||||
|
||||
auto phoneValue = QString();
|
||||
_code.create(this, st::defaultInputField, tr::lng_change_phone_code_title(), phoneValue);
|
||||
const auto phoneValue = QString();
|
||||
_code.create(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
tr::lng_change_phone_code_title(),
|
||||
phoneValue);
|
||||
_code->setAutoSubmit(_codeLength, [=] { submit(); });
|
||||
_code->setChangedCallback([=] { hideError(); });
|
||||
|
||||
@@ -289,7 +331,7 @@ void ChangePhoneBox::EnterCode::prepare() {
|
||||
setDimensions(st::boxWidth, countHeight());
|
||||
|
||||
if (_callTimeout > 0) {
|
||||
_call.setStatus({ SentCodeCall::State::Waiting, _callTimeout });
|
||||
_call.setStatus({ Ui::SentCodeCall::State::Waiting, _callTimeout });
|
||||
updateCall();
|
||||
}
|
||||
|
||||
@@ -298,7 +340,8 @@ void ChangePhoneBox::EnterCode::prepare() {
|
||||
}
|
||||
|
||||
int ChangePhoneBox::EnterCode::countHeight() {
|
||||
auto errorSkip = st::boxLittleSkip + st::changePhoneError.style.font->height;
|
||||
const auto errorSkip = st::boxLittleSkip
|
||||
+ st::changePhoneError.style.font->height;
|
||||
return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip;
|
||||
}
|
||||
|
||||
@@ -316,12 +359,14 @@ void ChangePhoneBox::EnterCode::submit() {
|
||||
MTP_string(_hash),
|
||||
MTP_string(code)
|
||||
)).done([=](const MTPUser &result) {
|
||||
_requestId = 0;
|
||||
session->data().processUser(result);
|
||||
if (weak) {
|
||||
Ui::hideLayer();
|
||||
}
|
||||
Ui::Toast::Show(tr::lng_change_phone_success(tr::now));
|
||||
}).fail(crl::guard(this, [=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
sendCodeFail(error);
|
||||
})).handleFloodErrors().send();
|
||||
}
|
||||
@@ -336,12 +381,14 @@ void ChangePhoneBox::EnterCode::sendCall() {
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterCode::updateCall() {
|
||||
auto text = _call.getText();
|
||||
const auto text = _call.getText();
|
||||
if (text.isEmpty()) {
|
||||
_callLabel.destroy();
|
||||
} else if (!_callLabel) {
|
||||
_callLabel.create(this, text, st::changePhoneLabel);
|
||||
_callLabel->moveToLeft(st::boxPadding.left(), countHeight() - _callLabel->height());
|
||||
_callLabel->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
countHeight() - _callLabel->height());
|
||||
_callLabel->show();
|
||||
} else {
|
||||
_callLabel->setText(text);
|
||||
@@ -349,17 +396,22 @@ void ChangePhoneBox::EnterCode::updateCall() {
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterCode::showError(const QString &text) {
|
||||
createErrorLabel(this, _error, text, st::boxPadding.left(), _code->y() + _code->height() + st::boxLittleSkip);
|
||||
CreateErrorLabel(
|
||||
this,
|
||||
_error,
|
||||
text,
|
||||
st::boxPadding.left(),
|
||||
_code->y() + _code->height() + st::boxLittleSkip);
|
||||
if (!text.isEmpty()) {
|
||||
_code->showError();
|
||||
}
|
||||
}
|
||||
|
||||
void ChangePhoneBox::EnterCode::sendCodeFail(const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
if (MTP::IsFloodError(error)) {
|
||||
showError(tr::lng_flood_error(tr::now));
|
||||
} else if (error.type() == qstr("PHONE_CODE_EMPTY") || error.type() == qstr("PHONE_CODE_INVALID")) {
|
||||
} else if (error.type() == qstr("PHONE_CODE_EMPTY")
|
||||
|| error.type() == qstr("PHONE_CODE_INVALID")) {
|
||||
showError(tr::lng_bad_code(tr::now));
|
||||
} else if (error.type() == qstr("PHONE_CODE_EXPIRED")
|
||||
|| error.type() == qstr("PHONE_NUMBER_BANNED")) {
|
||||
@@ -371,18 +423,25 @@ void ChangePhoneBox::EnterCode::sendCodeFail(const MTP::Error &error) {
|
||||
}
|
||||
}
|
||||
|
||||
ChangePhoneBox::ChangePhoneBox(QWidget*, not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
ChangePhoneBox::ChangePhoneBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: _controller(controller) {
|
||||
}
|
||||
|
||||
void ChangePhoneBox::prepare() {
|
||||
const auto session = _session;
|
||||
|
||||
setTitle(tr::lng_change_phone_title());
|
||||
addButton(tr::lng_change_phone_button(), [=] {
|
||||
Ui::show(Box<ConfirmBox>(tr::lng_change_phone_warning(tr::now), [=] {
|
||||
Ui::show(Box<EnterPhone>(session));
|
||||
}));
|
||||
addButton(tr::lng_change_phone_button(), [=, controller = _controller] {
|
||||
auto callback = [=] {
|
||||
controller->show(
|
||||
Box<EnterPhone>(controller),
|
||||
Ui::LayerOption::CloseOther);
|
||||
};
|
||||
controller->show(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_change_phone_warning(tr::now),
|
||||
std::move(callback)),
|
||||
Ui::LayerOption::CloseOther);
|
||||
});
|
||||
addButton(tr::lng_cancel(), [this] {
|
||||
closeBox();
|
||||
@@ -392,14 +451,22 @@ void ChangePhoneBox::prepare() {
|
||||
this,
|
||||
tr::lng_change_phone_about(Ui::Text::RichLangValue),
|
||||
st::changePhoneDescription);
|
||||
label->moveToLeft((st::boxWideWidth - label->width()) / 2, st::changePhoneDescriptionTop);
|
||||
label->moveToLeft(
|
||||
(st::boxWideWidth - label->width()) / 2,
|
||||
st::changePhoneDescriptionTop);
|
||||
|
||||
setDimensions(st::boxWideWidth, label->bottomNoMargins() + st::boxLittleSkip);
|
||||
setDimensions(
|
||||
st::boxWideWidth,
|
||||
label->bottomNoMargins() + st::boxLittleSkip);
|
||||
}
|
||||
|
||||
void ChangePhoneBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
st::changePhoneIcon.paint(p, (width() - st::changePhoneIcon.width()) / 2, st::changePhoneIconTop, width());
|
||||
st::changePhoneIcon.paint(
|
||||
p,
|
||||
(width() - st::changePhoneIcon.width()) / 2,
|
||||
st::changePhoneIconTop,
|
||||
width());
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
class ChangePhoneBox : public Ui::BoxContent {
|
||||
public:
|
||||
ChangePhoneBox(QWidget*, not_null<Main::Session*> session);
|
||||
ChangePhoneBox(QWidget*, not_null<Window::SessionController*> controller);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
class EnterPhone;
|
||||
class EnterCode;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,983 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/confirm_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "history/view/controls/history_view_ttl_button.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "facades.h" // Ui::showChatsList
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace {
|
||||
|
||||
TextParseOptions kInformBoxTextOptions = {
|
||||
(TextParseLinks
|
||||
| TextParseMultiline
|
||||
| TextParseMarkdown
|
||||
| TextParseRichText), // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions kMarkedTextBoxOptions = {
|
||||
(TextParseLinks
|
||||
| TextParseMultiline
|
||||
| TextParseMarkdown
|
||||
| TextParseRichText
|
||||
| TextParseMentions
|
||||
| TextParseHashtags), // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
[[nodiscard]] bool IsOldForPin(MsgId id, not_null<PeerData*> peer) {
|
||||
const auto normal = peer->migrateToOrMe();
|
||||
const auto migrated = normal->migrateFrom();
|
||||
const auto top = Data::ResolveTopPinnedId(normal, migrated);
|
||||
if (!top) {
|
||||
return false;
|
||||
} else if (peer == migrated) {
|
||||
return top.channel || (id < top.msg);
|
||||
} else if (migrated) {
|
||||
return top.channel && (id < top.msg);
|
||||
} else {
|
||||
return (id < top.msg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(tr::lng_box_ok(tr::now))
|
||||
, _cancelText(tr::lng_cancel(tr::now))
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(confirmText)
|
||||
, _cancelText(tr::lng_cancel(tr::now))
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const TextWithEntities &text,
|
||||
const QString &confirmText,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(confirmText)
|
||||
, _cancelText(tr::lng_cancel(tr::now))
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const style::RoundButton &confirmStyle,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(confirmText)
|
||||
, _cancelText(tr::lng_cancel(tr::now))
|
||||
, _confirmStyle(confirmStyle)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const QString &cancelText,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(confirmText)
|
||||
, _cancelText(cancelText)
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const style::RoundButton &confirmStyle,
|
||||
const QString &cancelText,
|
||||
ConfirmBox::ConfirmedCallback confirmedCallback,
|
||||
FnMut<void()> cancelledCallback)
|
||||
: _confirmText(confirmText)
|
||||
, _cancelText(cancelText)
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(std::move(confirmedCallback))
|
||||
, _cancelledCallback(std::move(cancelledCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
const InformBoxTag &,
|
||||
const QString &text,
|
||||
const QString &doneText,
|
||||
Fn<void()> closedCallback)
|
||||
: _confirmText(doneText)
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _informative(true)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(generateInformCallback(closedCallback))
|
||||
, _cancelledCallback(generateInformCallback(closedCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
ConfirmBox::ConfirmBox(
|
||||
const InformBoxTag &,
|
||||
const TextWithEntities &text,
|
||||
const QString &doneText,
|
||||
Fn<void()> closedCallback)
|
||||
: _confirmText(doneText)
|
||||
, _confirmStyle(st::defaultBoxButton)
|
||||
, _informative(true)
|
||||
, _text(st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right())
|
||||
, _confirmedCallback(generateInformCallback(closedCallback))
|
||||
, _cancelledCallback(generateInformCallback(closedCallback)) {
|
||||
init(text);
|
||||
}
|
||||
|
||||
FnMut<void()> ConfirmBox::generateInformCallback(
|
||||
Fn<void()> closedCallback) {
|
||||
return crl::guard(this, [=] {
|
||||
closeBox();
|
||||
if (closedCallback) {
|
||||
closedCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ConfirmBox::init(const QString &text) {
|
||||
_text.setText(
|
||||
st::boxLabelStyle,
|
||||
text,
|
||||
_informative ? kInformBoxTextOptions : _textPlainOptions);
|
||||
}
|
||||
|
||||
void ConfirmBox::init(const TextWithEntities &text) {
|
||||
_text.setMarkedText(st::boxLabelStyle, text, kMarkedTextBoxOptions);
|
||||
}
|
||||
|
||||
void ConfirmBox::prepare() {
|
||||
addButton(
|
||||
rpl::single(_confirmText),
|
||||
[=] { confirmed(); },
|
||||
_confirmStyle);
|
||||
if (!_informative) {
|
||||
addButton(
|
||||
rpl::single(_cancelText),
|
||||
[=] { _cancelled = true; closeBox(); });
|
||||
}
|
||||
|
||||
boxClosing() | rpl::start_with_next([=] {
|
||||
if (!_confirmed && (!_strictCancel || _cancelled)) {
|
||||
if (auto callback = std::move(_cancelledCallback)) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
textUpdated();
|
||||
}
|
||||
|
||||
void ConfirmBox::setMaxLineCount(int count) {
|
||||
if (_maxLineCount != count) {
|
||||
_maxLineCount = count;
|
||||
textUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmBox::textUpdated() {
|
||||
_textWidth = st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right();
|
||||
_textHeight = _text.countHeight(_textWidth);
|
||||
if (_maxLineCount > 0) {
|
||||
accumulate_min(_textHeight, _maxLineCount * st::boxLabelStyle.lineHeight);
|
||||
}
|
||||
setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom());
|
||||
|
||||
setMouseTracking(_text.hasLinks());
|
||||
}
|
||||
|
||||
void ConfirmBox::confirmed() {
|
||||
if (!_confirmed) {
|
||||
_confirmed = true;
|
||||
|
||||
const auto confirmed = &_confirmedCallback;
|
||||
if (const auto callbackPtr = std::get_if<1>(confirmed)) {
|
||||
if (auto callback = base::take(*callbackPtr)) {
|
||||
callback();
|
||||
}
|
||||
} else if (const auto callbackPtr = std::get_if<2>(confirmed)) {
|
||||
if (auto callback = base::take(*callbackPtr)) {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
callback(crl::guard(weak, [=] { closeBox(); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmBox::mouseMoveEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateHover();
|
||||
}
|
||||
|
||||
void ConfirmBox::mousePressEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateHover();
|
||||
ClickHandler::pressed();
|
||||
return BoxContent::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateHover();
|
||||
if (const auto activated = ClickHandler::unpressed()) {
|
||||
ActivateClickHandler(window(), activated, e->button());
|
||||
crl::on_main(this, [=] {
|
||||
closeBox();
|
||||
});
|
||||
return;
|
||||
}
|
||||
BoxContent::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void ConfirmBox::leaveEventHook(QEvent *e) {
|
||||
ClickHandler::clearActive(this);
|
||||
}
|
||||
|
||||
void ConfirmBox::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
setCursor(active ? style::cur_pointer : style::cur_default);
|
||||
update();
|
||||
}
|
||||
|
||||
void ConfirmBox::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||
update();
|
||||
}
|
||||
|
||||
void ConfirmBox::updateLink() {
|
||||
_lastMousePos = QCursor::pos();
|
||||
updateHover();
|
||||
}
|
||||
|
||||
void ConfirmBox::updateHover() {
|
||||
auto m = mapFromGlobal(_lastMousePos);
|
||||
auto state = _text.getStateLeft(m - QPoint(st::boxPadding.left(), st::boxPadding.top()), _textWidth, width());
|
||||
|
||||
ClickHandler::setActive(state.link, this);
|
||||
}
|
||||
|
||||
void ConfirmBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
confirmed();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
// draw box title / text
|
||||
p.setPen(st::boxTextFg);
|
||||
if (_maxLineCount > 0) {
|
||||
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), _maxLineCount, style::al_left);
|
||||
} else {
|
||||
_text.drawLeft(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), style::al_left);
|
||||
}
|
||||
}
|
||||
|
||||
InformBox::InformBox(QWidget*, const QString &text, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, tr::lng_box_ok(tr::now), std::move(closedCallback)) {
|
||||
}
|
||||
|
||||
InformBox::InformBox(QWidget*, const QString &text, const QString &doneText, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std::move(closedCallback)) {
|
||||
}
|
||||
|
||||
InformBox::InformBox(QWidget*, const TextWithEntities &text, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, tr::lng_box_ok(tr::now), std::move(closedCallback)) {
|
||||
}
|
||||
|
||||
InformBox::InformBox(QWidget*, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, doneText, std::move(closedCallback)) {
|
||||
}
|
||||
|
||||
MaxInviteBox::MaxInviteBox(QWidget*, not_null<ChannelData*> channel) : BoxContent()
|
||||
, _channel(channel)
|
||||
, _text(
|
||||
st::boxLabelStyle,
|
||||
tr::lng_participant_invite_sorry(
|
||||
tr::now,
|
||||
lt_count,
|
||||
channel->session().serverConfig().chatSizeMax),
|
||||
kInformBoxTextOptions,
|
||||
(st::boxWidth
|
||||
- st::boxPadding.left()
|
||||
- st::defaultBox.buttonPadding.right())) {
|
||||
}
|
||||
|
||||
void MaxInviteBox::prepare() {
|
||||
setMouseTracking(true);
|
||||
|
||||
addButton(tr::lng_box_ok(), [=] { closeBox(); });
|
||||
|
||||
_textWidth = st::boxWidth - st::boxPadding.left() - st::defaultBox.buttonPadding.right();
|
||||
_textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight);
|
||||
setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxTextFont->height + st::boxTextFont->height * 2 + st::newGroupLinkPadding.bottom());
|
||||
|
||||
_channel->session().changes().peerUpdates(
|
||||
_channel,
|
||||
Data::PeerUpdate::Flag::InviteLinks
|
||||
) | rpl::start_with_next([=] {
|
||||
rtlupdate(_invitationLink);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) {
|
||||
updateSelected(e->globalPos());
|
||||
}
|
||||
|
||||
void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
|
||||
mouseMoveEvent(e);
|
||||
if (_linkOver) {
|
||||
if (_channel->inviteLink().isEmpty()) {
|
||||
_channel->session().api().inviteLinks().create(_channel);
|
||||
} else {
|
||||
QGuiApplication::clipboard()->setText(_channel->inviteLink());
|
||||
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaxInviteBox::leaveEventHook(QEvent *e) {
|
||||
updateSelected(QCursor::pos());
|
||||
}
|
||||
|
||||
void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) {
|
||||
QPoint p(mapFromGlobal(cursorGlobalPosition));
|
||||
|
||||
bool linkOver = _invitationLink.contains(p);
|
||||
if (linkOver != _linkOver) {
|
||||
_linkOver = linkOver;
|
||||
update();
|
||||
setCursor(_linkOver ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
}
|
||||
|
||||
void MaxInviteBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
// draw box title / text
|
||||
p.setPen(st::boxTextFg);
|
||||
_text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left);
|
||||
|
||||
QTextOption option(style::al_left);
|
||||
option.setWrapMode(QTextOption::WrapAnywhere);
|
||||
p.setFont(_linkOver ? st::defaultInputField.font->underline() : st::defaultInputField.font);
|
||||
p.setPen(st::defaultLinkButton.color);
|
||||
auto inviteLinkText = _channel->inviteLink().isEmpty() ? tr::lng_group_invite_create(tr::now) : _channel->inviteLink();
|
||||
p.drawText(_invitationLink, inviteLinkText, option);
|
||||
}
|
||||
|
||||
void MaxInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_invitationLink = myrtlrect(st::boxPadding.left(), st::boxPadding.top() + _textHeight + st::boxTextFont->height, width() - st::boxPadding.left() - st::boxPadding.right(), 2 * st::boxTextFont->height);
|
||||
}
|
||||
|
||||
PinMessageBox::PinMessageBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().mtp())
|
||||
, _msgId(msgId)
|
||||
, _pinningOld(IsOldForPin(msgId, peer))
|
||||
, _text(
|
||||
this,
|
||||
(_pinningOld
|
||||
? tr::lng_pinned_pin_old_sure(tr::now)
|
||||
: (peer->isChat() || peer->isMegagroup())
|
||||
? tr::lng_pinned_pin_sure_group(tr::now)
|
||||
: tr::lng_pinned_pin_sure(tr::now)),
|
||||
st::boxLabel) {
|
||||
}
|
||||
|
||||
void PinMessageBox::prepare() {
|
||||
addButton(tr::lng_pinned_pin(), [this] { pinMessage(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
|
||||
if (_peer->isUser() && !_peer->isSelf()) {
|
||||
_pinForPeer.create(
|
||||
this,
|
||||
tr::lng_pinned_also_for_other(
|
||||
tr::now,
|
||||
lt_user,
|
||||
_peer->shortName()),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
_checkbox = _pinForPeer;
|
||||
} else if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) {
|
||||
_notify.create(
|
||||
this,
|
||||
tr::lng_pinned_notify(tr::now),
|
||||
true,
|
||||
st::defaultBoxCheckbox);
|
||||
_checkbox = _notify;
|
||||
}
|
||||
|
||||
auto height = st::boxPadding.top() + _text->height() + st::boxPadding.bottom();
|
||||
if (_checkbox) {
|
||||
height += st::boxMediumSkip + _checkbox->heightNoMargins();
|
||||
}
|
||||
setDimensions(st::boxWidth, height);
|
||||
}
|
||||
|
||||
void PinMessageBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
|
||||
if (_checkbox) {
|
||||
_checkbox->moveToLeft(st::boxPadding.left(), _text->y() + _text->height() + st::boxMediumSkip);
|
||||
}
|
||||
}
|
||||
|
||||
void PinMessageBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
pinMessage();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void PinMessageBox::pinMessage() {
|
||||
if (_requestId) return;
|
||||
|
||||
auto flags = MTPmessages_UpdatePinnedMessage::Flags(0);
|
||||
if (_notify && !_notify->checked()) {
|
||||
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_silent;
|
||||
}
|
||||
if (_pinForPeer && !_pinForPeer->checked()) {
|
||||
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_pm_oneside;
|
||||
}
|
||||
_requestId = _api.request(MTPmessages_UpdatePinnedMessage(
|
||||
MTP_flags(flags),
|
||||
_peer->input,
|
||||
MTP_int(_msgId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
Ui::hideLayer();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::hideLayer();
|
||||
}).send();
|
||||
}
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<HistoryItem*> item,
|
||||
bool suggestModerateActions)
|
||||
: _session(&item->history()->session())
|
||||
, _ids(1, item->fullId()) {
|
||||
if (suggestModerateActions) {
|
||||
_moderateBan = item->suggestBanReport();
|
||||
_moderateDeleteAll = item->suggestDeleteAllReport();
|
||||
if (_moderateBan || _moderateDeleteAll) {
|
||||
_moderateFrom = item->from()->asUser();
|
||||
_moderateInChannel = item->history()->peer->asChannel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
MessageIdsList &&selected)
|
||||
: _session(session)
|
||||
, _ids(std::move(selected)) {
|
||||
Expects(!_ids.empty());
|
||||
}
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear)
|
||||
: _session(&peer->session())
|
||||
, _wipeHistoryPeer(peer)
|
||||
, _wipeHistoryJustClear(justClear) {
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::prepare() {
|
||||
auto details = TextWithEntities();
|
||||
const auto appendDetails = [&](TextWithEntities &&text) {
|
||||
details.append(qstr("\n\n")).append(std::move(text));
|
||||
};
|
||||
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
|
||||
*deleteText = tr::lng_box_delete();
|
||||
auto deleteStyle = &st::defaultBoxButton;
|
||||
auto canDelete = true;
|
||||
if (const auto peer = _wipeHistoryPeer) {
|
||||
if (_wipeHistoryJustClear) {
|
||||
const auto isChannel = peer->isBroadcast();
|
||||
const auto isPublicGroup = peer->isMegagroup()
|
||||
&& peer->asChannel()->isPublic();
|
||||
if (isChannel || isPublicGroup) {
|
||||
canDelete = false;
|
||||
}
|
||||
details.text = isChannel
|
||||
? tr::lng_no_clear_history_channel(tr::now)
|
||||
: isPublicGroup
|
||||
? tr::lng_no_clear_history_group(tr::now)
|
||||
: peer->isSelf()
|
||||
? tr::lng_sure_delete_saved_messages(tr::now)
|
||||
: peer->isUser()
|
||||
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
|
||||
: tr::lng_sure_delete_group_history(tr::now, lt_group, peer->name);
|
||||
deleteStyle = &st::attentionBoxButton;
|
||||
} else {
|
||||
details.text = peer->isSelf()
|
||||
? tr::lng_sure_delete_saved_messages(tr::now)
|
||||
: peer->isUser()
|
||||
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
|
||||
: peer->isChat()
|
||||
? tr::lng_sure_delete_and_exit(tr::now, lt_group, peer->name)
|
||||
: peer->isMegagroup()
|
||||
? tr::lng_sure_leave_group(tr::now)
|
||||
: tr::lng_sure_leave_channel(tr::now);
|
||||
if (!peer->isUser()) {
|
||||
*deleteText = tr::lng_box_leave();
|
||||
}
|
||||
deleteStyle = &st::attentionBoxButton;
|
||||
}
|
||||
if (auto revoke = revokeText(peer)) {
|
||||
_revoke.create(this, revoke->checkbox, false, st::defaultBoxCheckbox);
|
||||
appendDetails(std::move(revoke->description));
|
||||
if (!peer->isUser() && !_wipeHistoryJustClear) {
|
||||
_revoke->checkedValue(
|
||||
) | rpl::start_with_next([=](bool revokeForAll) {
|
||||
*deleteText = revokeForAll
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_box_leave();
|
||||
}, _revoke->lifetime());
|
||||
}
|
||||
}
|
||||
} else if (_moderateFrom) {
|
||||
Assert(_moderateInChannel != nullptr);
|
||||
|
||||
details.text = tr::lng_selected_delete_sure_this(tr::now);
|
||||
if (_moderateBan) {
|
||||
_banUser.create(this, tr::lng_ban_user(tr::now), false, st::defaultBoxCheckbox);
|
||||
}
|
||||
_reportSpam.create(this, tr::lng_report_spam(tr::now), false, st::defaultBoxCheckbox);
|
||||
if (_moderateDeleteAll) {
|
||||
_deleteAll.create(this, tr::lng_delete_all_from(tr::now), false, st::defaultBoxCheckbox);
|
||||
}
|
||||
} else {
|
||||
details.text = (_ids.size() == 1)
|
||||
? tr::lng_selected_delete_sure_this(tr::now)
|
||||
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
|
||||
if (const auto peer = checkFromSinglePeer()) {
|
||||
auto count = int(_ids.size());
|
||||
if (hasScheduledMessages()) {
|
||||
} else if (auto revoke = revokeText(peer)) {
|
||||
_revoke.create(this, revoke->checkbox, false, st::defaultBoxCheckbox);
|
||||
appendDetails(std::move(revoke->description));
|
||||
} else if (peer->isChannel()) {
|
||||
if (peer->isMegagroup()) {
|
||||
appendDetails({ tr::lng_delete_for_everyone_hint(tr::now, lt_count, count) });
|
||||
}
|
||||
} else if (peer->isChat()) {
|
||||
appendDetails({ tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count) });
|
||||
} else if (!peer->isSelf()) {
|
||||
appendDetails({ tr::lng_delete_for_me_hint(tr::now, lt_count, count) });
|
||||
}
|
||||
}
|
||||
}
|
||||
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
|
||||
|
||||
if (_wipeHistoryJustClear
|
||||
&& _wipeHistoryPeer
|
||||
&& ((_wipeHistoryPeer->isUser()
|
||||
&& !_wipeHistoryPeer->isSelf()
|
||||
&& !_wipeHistoryPeer->isNotificationsUser())
|
||||
|| (_wipeHistoryPeer->isChat()
|
||||
&& _wipeHistoryPeer->asChat()->canDeleteMessages())
|
||||
|| (_wipeHistoryPeer->isChannel()
|
||||
&& _wipeHistoryPeer->asChannel()->canDeleteMessages()))) {
|
||||
_wipeHistoryPeer->updateFull();
|
||||
_autoDeleteSettings.create(
|
||||
this,
|
||||
(_wipeHistoryPeer->messagesTTL()
|
||||
? tr::lng_edit_auto_delete_settings(tr::now)
|
||||
: tr::lng_enable_auto_delete(tr::now)),
|
||||
st::boxLinkButton);
|
||||
_autoDeleteSettings->setClickedCallback([=] {
|
||||
getDelegate()->show(
|
||||
Box(
|
||||
HistoryView::Controls::AutoDeleteSettingsBox,
|
||||
_wipeHistoryPeer),
|
||||
Ui::LayerOption(0));
|
||||
});
|
||||
}
|
||||
|
||||
if (canDelete) {
|
||||
addButton(
|
||||
deleteText->value(),
|
||||
[=] { deleteAndClear(); },
|
||||
*deleteStyle);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
} else {
|
||||
addButton(tr::lng_about_done(), [=] { closeBox(); });
|
||||
}
|
||||
|
||||
auto fullHeight = st::boxPadding.top() + _text->height() + st::boxPadding.bottom();
|
||||
if (_moderateFrom) {
|
||||
fullHeight += st::boxMediumSkip;
|
||||
if (_banUser) {
|
||||
fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
fullHeight += _reportSpam->heightNoMargins();
|
||||
if (_deleteAll) {
|
||||
fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
|
||||
}
|
||||
} else if (_revoke) {
|
||||
fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
|
||||
}
|
||||
if (_autoDeleteSettings) {
|
||||
fullHeight += st::boxMediumSkip + _autoDeleteSettings->height() + st::boxLittleSkip;
|
||||
}
|
||||
setDimensions(st::boxWidth, fullHeight);
|
||||
}
|
||||
|
||||
bool DeleteMessagesBox::hasScheduledMessages() const {
|
||||
for (const auto &fullId : _ids) {
|
||||
if (const auto item = _session->data().message(fullId)) {
|
||||
if (item->isScheduled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
|
||||
auto result = (PeerData*)nullptr;
|
||||
for (const auto &fullId : _ids) {
|
||||
if (const auto item = _session->data().message(fullId)) {
|
||||
const auto peer = item->history()->peer;
|
||||
if (!result) {
|
||||
result = peer;
|
||||
} else if (result != peer) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
|
||||
-> std::optional<RevokeConfig> {
|
||||
auto result = RevokeConfig();
|
||||
if (peer == _wipeHistoryPeer) {
|
||||
if (!peer->canRevokeFullHistory()) {
|
||||
return std::nullopt;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
result.checkbox = tr::lng_delete_for_other_check(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->firstName);
|
||||
} else if (_wipeHistoryJustClear) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto items = ranges::views::all(
|
||||
_ids
|
||||
) | ranges::views::transform([&](FullMsgId id) {
|
||||
return peer->owner().message(id);
|
||||
}) | ranges::views::filter([](HistoryItem *item) {
|
||||
return (item != nullptr);
|
||||
}) | ranges::to_vector;
|
||||
|
||||
if (items.size() != _ids.size()) {
|
||||
// We don't have information about all messages.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto canRevoke = [&](HistoryItem * item) {
|
||||
return item->canDeleteForEveryone(now);
|
||||
};
|
||||
const auto cannotRevoke = [&](HistoryItem *item) {
|
||||
return !item->canDeleteForEveryone(now);
|
||||
};
|
||||
const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
|
||||
auto outgoing = items | ranges::views::filter(&HistoryItem::out);
|
||||
const auto canRevokeOutgoingCount = canRevokeAll
|
||||
? -1
|
||||
: ranges::count_if(outgoing, canRevoke);
|
||||
|
||||
if (canRevokeAll) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
result.checkbox = tr::lng_delete_for_other_check(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->firstName);
|
||||
} else {
|
||||
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
|
||||
}
|
||||
return result;
|
||||
} else if (canRevokeOutgoingCount > 0) {
|
||||
result.checkbox = tr::lng_delete_for_other_my(tr::now);
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (canRevokeOutgoingCount == 1) {
|
||||
result.description = tr::lng_selected_unsend_about_user_one(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->shortName()),
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.description = tr::lng_selected_unsend_about_user(
|
||||
tr::now,
|
||||
lt_count,
|
||||
canRevokeOutgoingCount,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->shortName()),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
} else if (canRevokeOutgoingCount == 1) {
|
||||
result.description = tr::lng_selected_unsend_about_group_one(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.description = tr::lng_selected_unsend_about_group(
|
||||
tr::now,
|
||||
lt_count,
|
||||
canRevokeOutgoingCount,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
|
||||
auto top = _text->bottomNoMargins() + st::boxMediumSkip;
|
||||
if (_moderateFrom) {
|
||||
if (_banUser) {
|
||||
_banUser->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _banUser->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
_reportSpam->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
|
||||
if (_deleteAll) {
|
||||
_deleteAll->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
} else if (_revoke) {
|
||||
const auto availableWidth = width() - 2 * st::boxPadding.left();
|
||||
_revoke->resizeToNaturalWidth(availableWidth);
|
||||
_revoke->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _revoke->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
if (_autoDeleteSettings) {
|
||||
top += st::boxMediumSkip - st::boxLittleSkip;
|
||||
_autoDeleteSettings->moveToLeft(st::boxPadding.left(), top);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
// Don't make the clearing history so easy.
|
||||
if (!_wipeHistoryPeer) {
|
||||
deleteAndClear();
|
||||
}
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::deleteAndClear() {
|
||||
const auto revoke = _revoke ? _revoke->checked() : false;
|
||||
if (const auto peer = _wipeHistoryPeer) {
|
||||
const auto justClear = _wipeHistoryJustClear;
|
||||
closeBox();
|
||||
|
||||
if (justClear) {
|
||||
peer->session().api().clearHistory(peer, revoke);
|
||||
} else {
|
||||
for (const auto &controller : peer->session().windows()) {
|
||||
if (controller->activeChatCurrent().peer() == peer) {
|
||||
Ui::showChatsList(&peer->session());
|
||||
}
|
||||
}
|
||||
// Don't delete old history by default,
|
||||
// because Android app doesn't.
|
||||
//
|
||||
//if (const auto from = peer->migrateFrom()) {
|
||||
// peer->session().api().deleteConversation(from, false);
|
||||
//}
|
||||
peer->session().api().deleteConversation(peer, revoke);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_moderateFrom) {
|
||||
if (_banUser && _banUser->checked()) {
|
||||
_moderateInChannel->session().api().kickParticipant(
|
||||
_moderateInChannel,
|
||||
_moderateFrom,
|
||||
ChatRestrictionsInfo());
|
||||
}
|
||||
if (_reportSpam->checked()) {
|
||||
_moderateInChannel->session().api().request(
|
||||
MTPchannels_ReportSpam(
|
||||
_moderateInChannel->inputChannel,
|
||||
_moderateFrom->inputUser,
|
||||
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
|
||||
).send();
|
||||
}
|
||||
if (_deleteAll && _deleteAll->checked()) {
|
||||
_moderateInChannel->session().api().deleteAllFromUser(
|
||||
_moderateInChannel,
|
||||
_moderateFrom);
|
||||
}
|
||||
}
|
||||
|
||||
if (_deleteConfirmedCallback) {
|
||||
_deleteConfirmedCallback();
|
||||
}
|
||||
|
||||
// deleteMessages can initiate closing of the current section,
|
||||
// which will cause this box to be destroyed.
|
||||
const auto session = _session;
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
|
||||
session->data().histories().deleteMessages(_ids, revoke);
|
||||
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
ConfirmDontWarnBox::ConfirmDontWarnBox(
|
||||
QWidget*,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const QString &checkbox,
|
||||
rpl::producer<QString> confirm,
|
||||
FnMut<void(bool)> callback)
|
||||
: _confirm(std::move(confirm))
|
||||
, _content(setupContent(std::move(text), checkbox, std::move(callback))) {
|
||||
}
|
||||
|
||||
void ConfirmDontWarnBox::prepare() {
|
||||
setDimensionsToContent(st::boxWidth, _content);
|
||||
addButton(std::move(_confirm), [=] { _callback(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> ConfirmDontWarnBox::setupContent(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const QString &checkbox,
|
||||
FnMut<void(bool)> callback) {
|
||||
const auto result = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
result->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
result,
|
||||
std::move(text),
|
||||
st::boxLabel),
|
||||
st::boxPadding);
|
||||
const auto control = result->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
result,
|
||||
checkbox,
|
||||
false,
|
||||
st::defaultBoxCheckbox),
|
||||
style::margins(
|
||||
st::boxPadding.left(),
|
||||
st::boxPadding.bottom(),
|
||||
st::boxPadding.right(),
|
||||
st::boxPadding.bottom()));
|
||||
_callback = [=, callback = std::move(callback)]() mutable {
|
||||
const auto checked = control->checked();
|
||||
auto local = std::move(callback);
|
||||
closeBox();
|
||||
local(checked);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class Checkbox;
|
||||
class FlatLabel;
|
||||
class EmptyUserpic;
|
||||
class LinkButton;
|
||||
} // namespace Ui
|
||||
|
||||
class InformBox;
|
||||
class ConfirmBox : public Ui::BoxContent, public ClickHandlerHost {
|
||||
public:
|
||||
|
||||
using ConfirmedCallback = std::variant<
|
||||
v::null_t,
|
||||
FnMut<void()>,
|
||||
FnMut<void(Fn<void()>)>>;
|
||||
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
ConfirmedCallback confirmedCallback = FnMut<void()>(),
|
||||
FnMut<void()> cancelledCallback = FnMut<void()>());
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
ConfirmedCallback confirmedCallback = FnMut<void()>(),
|
||||
FnMut<void()> cancelledCallback = FnMut<void()>());
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const style::RoundButton &confirmStyle,
|
||||
ConfirmedCallback confirmedCallback = FnMut<void()>(),
|
||||
FnMut<void()> cancelledCallback = FnMut<void()>());
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const QString &cancelText,
|
||||
ConfirmedCallback confirmedCallback = FnMut<void()>(),
|
||||
FnMut<void()> cancelledCallback = FnMut<void()>());
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &confirmText,
|
||||
const style::RoundButton &confirmStyle,
|
||||
const QString &cancelText,
|
||||
ConfirmedCallback confirmedCallback = FnMut<void()>(),
|
||||
FnMut<void()> cancelledCallback = FnMut<void()>());
|
||||
ConfirmBox(
|
||||
QWidget*,
|
||||
const TextWithEntities &text,
|
||||
const QString &confirmText,
|
||||
ConfirmedCallback confirmedCallback = v::null,
|
||||
FnMut<void()> cancelledCallback = nullptr);
|
||||
|
||||
void updateLink();
|
||||
|
||||
// If strict cancel is set the cancelledCallback is only called if the cancel button was pressed.
|
||||
void setStrictCancel(bool strictCancel) {
|
||||
_strictCancel = strictCancel;
|
||||
}
|
||||
|
||||
void setMaxLineCount(int count);
|
||||
|
||||
// ClickHandlerHost interface
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
private:
|
||||
struct InformBoxTag {
|
||||
};
|
||||
ConfirmBox(const InformBoxTag &, const QString &text, const QString &doneText, Fn<void()> closedCallback);
|
||||
ConfirmBox(const InformBoxTag &, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback);
|
||||
FnMut<void()> generateInformCallback(Fn<void()> closedCallback);
|
||||
friend class InformBox;
|
||||
|
||||
void confirmed();
|
||||
void init(const QString &text);
|
||||
void init(const TextWithEntities &text);
|
||||
void textUpdated();
|
||||
void updateHover();
|
||||
|
||||
QString _confirmText;
|
||||
QString _cancelText;
|
||||
const style::RoundButton &_confirmStyle;
|
||||
bool _informative = false;
|
||||
|
||||
Ui::Text::String _text;
|
||||
int _textWidth = 0;
|
||||
int _textHeight = 0;
|
||||
int _maxLineCount = 16;
|
||||
|
||||
QPoint _lastMousePos;
|
||||
|
||||
bool _confirmed = false;
|
||||
bool _cancelled = false;
|
||||
bool _strictCancel = false;
|
||||
ConfirmBox::ConfirmedCallback _confirmedCallback;
|
||||
FnMut<void()> _cancelledCallback;
|
||||
|
||||
};
|
||||
|
||||
class InformBox : public ConfirmBox {
|
||||
public:
|
||||
InformBox(QWidget*, const QString &text, Fn<void()> closedCallback = nullptr);
|
||||
InformBox(QWidget*, const QString &text, const QString &doneText, Fn<void()> closedCallback = nullptr);
|
||||
InformBox(QWidget*, const TextWithEntities &text, Fn<void()> closedCallback = nullptr);
|
||||
InformBox(QWidget*, const TextWithEntities &text, const QString &doneText, Fn<void()> closedCallback = nullptr);
|
||||
|
||||
};
|
||||
|
||||
class MaxInviteBox final : public Ui::BoxContent {
|
||||
public:
|
||||
MaxInviteBox(QWidget*, not_null<ChannelData*> channel);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateSelected(const QPoint &cursorGlobalPosition);
|
||||
|
||||
not_null<ChannelData*> _channel;
|
||||
|
||||
Ui::Text::String _text;
|
||||
int32 _textWidth, _textHeight;
|
||||
|
||||
QRect _invitationLink;
|
||||
bool _linkOver = false;
|
||||
|
||||
QPoint _lastMousePos;
|
||||
|
||||
};
|
||||
|
||||
class PinMessageBox final : public Ui::BoxContent {
|
||||
public:
|
||||
PinMessageBox(QWidget*, not_null<PeerData*> peer, MsgId msgId);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
void pinMessage();
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
MsgId _msgId = 0;
|
||||
bool _pinningOld = false;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _text;
|
||||
object_ptr<Ui::Checkbox> _notify = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _pinForPeer = { nullptr };
|
||||
QPointer<Ui::Checkbox> _checkbox;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
class DeleteMessagesBox final : public Ui::BoxContent {
|
||||
public:
|
||||
DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<HistoryItem*> item,
|
||||
bool suggestModerateActions);
|
||||
DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
MessageIdsList &&selected);
|
||||
DeleteMessagesBox(QWidget*, not_null<PeerData*> peer, bool justClear);
|
||||
|
||||
void setDeleteConfirmedCallback(Fn<void()> callback) {
|
||||
_deleteConfirmedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
struct RevokeConfig {
|
||||
QString checkbox;
|
||||
TextWithEntities description;
|
||||
};
|
||||
void deleteAndClear();
|
||||
[[nodiscard]] PeerData *checkFromSinglePeer() const;
|
||||
[[nodiscard]] bool hasScheduledMessages() const;
|
||||
[[nodiscard]] std::optional<RevokeConfig> revokeText(
|
||||
not_null<PeerData*> peer) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
PeerData * const _wipeHistoryPeer = nullptr;
|
||||
const bool _wipeHistoryJustClear = false;
|
||||
const MessageIdsList _ids;
|
||||
UserData *_moderateFrom = nullptr;
|
||||
ChannelData *_moderateInChannel = nullptr;
|
||||
bool _moderateBan = false;
|
||||
bool _moderateDeleteAll = false;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _text = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _revoke = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _banUser = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _reportSpam = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _deleteAll = { nullptr };
|
||||
object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr };
|
||||
|
||||
Fn<void()> _deleteConfirmedCallback;
|
||||
|
||||
};
|
||||
|
||||
class ConfirmDontWarnBox : public Ui::BoxContent {
|
||||
public:
|
||||
ConfirmDontWarnBox(
|
||||
QWidget*,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const QString &checkbox,
|
||||
rpl::producer<QString> confirm,
|
||||
FnMut<void(bool)> callback);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
not_null<Ui::RpWidget*> setupContent(
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const QString &checkbox,
|
||||
FnMut<void(bool)> callback);
|
||||
|
||||
rpl::producer<QString> _confirm;
|
||||
FnMut<void()> _callback;
|
||||
not_null<Ui::RpWidget*> _content;
|
||||
|
||||
};
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/confirm_phone_box.h"
|
||||
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "core/click_handler_types.h" // UrlClickHandler
|
||||
#include "base/qthelp_url.h" // qthelp::url_encode
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
object_ptr<ConfirmPhoneBox> CurrentConfirmPhoneBox = { nullptr };
|
||||
|
||||
void SendToBannedHelp(const QString &phone) {
|
||||
const auto version = QString::fromLatin1(AppVersionStr)
|
||||
+ (cAlphaVersion()
|
||||
? qsl(" alpha %1").arg(cAlphaVersion())
|
||||
: (AppBetaVersion ? " beta" : ""));
|
||||
|
||||
const auto subject = qsl("Banned phone number: ") + phone;
|
||||
|
||||
const auto body = qsl("\
|
||||
I'm trying to use my mobile phone number: ") + phone + qsl("\n\
|
||||
But Telegram says it's banned. Please help.\n\
|
||||
\n\
|
||||
App version: ") + version + qsl("\n\
|
||||
OS version: ") + Platform::SystemVersionPretty() + qsl("\n\
|
||||
Locale: ") + Platform::SystemLanguage();
|
||||
|
||||
const auto url = "mailto:?to="
|
||||
+ qthelp::url_encode("login@stel.com")
|
||||
+ "&subject="
|
||||
+ qthelp::url_encode(subject)
|
||||
+ "&body="
|
||||
+ qthelp::url_encode(body);
|
||||
|
||||
UrlClickHandler::Open(url);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowPhoneBannedError(const QString &phone) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto close = [=] {
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
};
|
||||
*box = Ui::show(Box<ConfirmBox>(
|
||||
tr::lng_signin_banned_text(tr::now),
|
||||
tr::lng_box_ok(tr::now),
|
||||
tr::lng_signin_banned_help(tr::now),
|
||||
close,
|
||||
[=] { SendToBannedHelp(phone); close(); }));
|
||||
}
|
||||
|
||||
SentCodeField::SentCodeField(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
rpl::producer<QString> placeholder,
|
||||
const QString &val)
|
||||
: Ui::InputField(parent, st, std::move(placeholder), val) {
|
||||
connect(this, &Ui::InputField::changed, [this] { fix(); });
|
||||
}
|
||||
|
||||
void SentCodeField::setAutoSubmit(int length, Fn<void()> submitCallback) {
|
||||
_autoSubmitLength = length;
|
||||
_submitCallback = std::move(submitCallback);
|
||||
}
|
||||
|
||||
void SentCodeField::setChangedCallback(Fn<void()> changedCallback) {
|
||||
_changedCallback = std::move(changedCallback);
|
||||
}
|
||||
|
||||
QString SentCodeField::getDigitsOnly() const {
|
||||
return QString(
|
||||
getLastText()
|
||||
).remove(
|
||||
QRegularExpression("[^\\d]")
|
||||
);
|
||||
}
|
||||
|
||||
void SentCodeField::fix() {
|
||||
if (_fixing) return;
|
||||
|
||||
_fixing = true;
|
||||
auto newText = QString();
|
||||
const auto now = getLastText();
|
||||
auto oldPos = textCursor().position();
|
||||
auto newPos = -1;
|
||||
auto oldLen = now.size();
|
||||
auto digitCount = 0;
|
||||
for (const auto &ch : now) {
|
||||
if (ch.isDigit()) {
|
||||
++digitCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (_autoSubmitLength > 0 && digitCount > _autoSubmitLength) {
|
||||
digitCount = _autoSubmitLength;
|
||||
}
|
||||
auto strict = (_autoSubmitLength > 0)
|
||||
&& (digitCount == _autoSubmitLength);
|
||||
|
||||
newText.reserve(oldLen);
|
||||
int i = 0;
|
||||
for (const auto &ch : now) {
|
||||
if (i++ == oldPos) {
|
||||
newPos = newText.length();
|
||||
}
|
||||
if (ch.isDigit()) {
|
||||
if (!digitCount--) {
|
||||
break;
|
||||
}
|
||||
newText += ch;
|
||||
if (strict && !digitCount) {
|
||||
break;
|
||||
}
|
||||
} else if (ch == '-') {
|
||||
newText += ch;
|
||||
}
|
||||
}
|
||||
if (newPos < 0) {
|
||||
newPos = newText.length();
|
||||
}
|
||||
if (newText != now) {
|
||||
setText(newText);
|
||||
setCursorPosition(newPos);
|
||||
}
|
||||
_fixing = false;
|
||||
|
||||
if (_changedCallback) {
|
||||
_changedCallback();
|
||||
}
|
||||
if (strict && _submitCallback) {
|
||||
_submitCallback();
|
||||
}
|
||||
}
|
||||
|
||||
SentCodeCall::SentCodeCall(
|
||||
FnMut<void()> callCallback,
|
||||
Fn<void()> updateCallback)
|
||||
: _call(std::move(callCallback))
|
||||
, _update(std::move(updateCallback)) {
|
||||
_timer.setCallback([=] {
|
||||
if (_status.state == State::Waiting) {
|
||||
if (--_status.timeout <= 0) {
|
||||
_status.state = State::Calling;
|
||||
_timer.cancel();
|
||||
if (_call) {
|
||||
_call();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_update) {
|
||||
_update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SentCodeCall::setStatus(const Status &status) {
|
||||
_status = status;
|
||||
if (_status.state == State::Waiting) {
|
||||
_timer.callEach(1000);
|
||||
}
|
||||
}
|
||||
|
||||
QString SentCodeCall::getText() const {
|
||||
switch (_status.state) {
|
||||
case State::Waiting: {
|
||||
if (_status.timeout >= 3600) {
|
||||
return tr::lng_code_call(tr::now, lt_minutes, qsl("%1:%2").arg(_status.timeout / 3600).arg((_status.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0')));
|
||||
}
|
||||
return tr::lng_code_call(tr::now, lt_minutes, QString::number(_status.timeout / 60), lt_seconds, qsl("%1").arg(_status.timeout % 60, 2, 10, QChar('0')));
|
||||
} break;
|
||||
case State::Calling: return tr::lng_code_calling(tr::now);
|
||||
case State::Called: return tr::lng_code_called(tr::now);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::Start(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &phone,
|
||||
const QString &hash) {
|
||||
if (CurrentConfirmPhoneBox
|
||||
&& (CurrentConfirmPhoneBox->getPhone() != phone
|
||||
|| &CurrentConfirmPhoneBox->session() != session)) {
|
||||
CurrentConfirmPhoneBox.destroyDelayed();
|
||||
}
|
||||
if (!CurrentConfirmPhoneBox) {
|
||||
CurrentConfirmPhoneBox = Box<ConfirmPhoneBox>(session, phone, hash);
|
||||
}
|
||||
CurrentConfirmPhoneBox->checkPhoneAndHash();
|
||||
}
|
||||
|
||||
ConfirmPhoneBox::ConfirmPhoneBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const QString &phone,
|
||||
const QString &hash)
|
||||
: _session(session)
|
||||
, _api(&session->mtp())
|
||||
, _phone(phone)
|
||||
, _hash(hash)
|
||||
, _call([this] { sendCall(); }, [this] { update(); }) {
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::sendCall() {
|
||||
_api.request(MTPauth_ResendCode(
|
||||
MTP_string(_phone),
|
||||
MTP_string(_phoneHash)
|
||||
)).done([=](const MTPauth_SentCode &result) {
|
||||
callDone(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::checkPhoneAndHash() {
|
||||
if (_sendCodeRequestId) {
|
||||
return;
|
||||
}
|
||||
_sendCodeRequestId = _api.request(MTPaccount_SendConfirmPhoneCode(
|
||||
MTP_string(_hash),
|
||||
MTP_codeSettings(MTP_flags(0))
|
||||
)).done([=](const MTPauth_SentCode &result) {
|
||||
sendCodeDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
sendCodeFail(error);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) {
|
||||
result.match([&](const MTPDauth_sentCode &data) {
|
||||
_sendCodeRequestId = 0;
|
||||
_sentCodeLength = data.vtype().match([&](const MTPDauth_sentCodeTypeApp &data) {
|
||||
LOG(("Error: should not be in-app code!"));
|
||||
return 0;
|
||||
}, [&](const MTPDauth_sentCodeTypeSms &data) {
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &data) {
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &data) {
|
||||
LOG(("Error: should not be flashcall!"));
|
||||
return 0;
|
||||
});
|
||||
_phoneHash = qs(data.vphone_code_hash());
|
||||
if (const auto nextType = data.vnext_type()) {
|
||||
if (nextType->type() == mtpc_auth_codeTypeCall) {
|
||||
_call.setStatus({ SentCodeCall::State::Waiting, data.vtimeout().value_or(60) });
|
||||
}
|
||||
}
|
||||
launch();
|
||||
});
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::sendCodeFail(const MTP::Error &error) {
|
||||
auto errorText = Lang::Hard::ServerError();
|
||||
if (MTP::IsFloodError(error)) {
|
||||
errorText = tr::lng_flood_error(tr::now);
|
||||
} else if (error.code() == 400) {
|
||||
errorText = tr::lng_confirm_phone_link_invalid(tr::now);
|
||||
}
|
||||
_sendCodeRequestId = 0;
|
||||
Ui::show(Box<InformBox>(errorText));
|
||||
if (this == CurrentConfirmPhoneBox) {
|
||||
CurrentConfirmPhoneBox.destroyDelayed();
|
||||
} else {
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::launch() {
|
||||
if (!CurrentConfirmPhoneBox) return;
|
||||
Ui::show(std::move(CurrentConfirmPhoneBox));
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::prepare() {
|
||||
_about.create(
|
||||
this,
|
||||
tr::lng_confirm_phone_about(
|
||||
lt_phone,
|
||||
rpl::single(Ui::Text::Bold(Ui::FormatPhone(_phone))),
|
||||
Ui::Text::WithEntities),
|
||||
st::confirmPhoneAboutLabel);
|
||||
|
||||
_code.create(this, st::confirmPhoneCodeField, tr::lng_code_ph());
|
||||
_code->setAutoSubmit(_sentCodeLength, [=] { sendCode(); });
|
||||
_code->setChangedCallback([=] { showError(QString()); });
|
||||
|
||||
setTitle(tr::lng_confirm_phone_title());
|
||||
|
||||
addButton(tr::lng_confirm_phone_send(), [=] { sendCode(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
setDimensions(st::boxWidth, st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip);
|
||||
|
||||
connect(_code, &Ui::InputField::submitted, [=] { sendCode(); });
|
||||
|
||||
showChildren();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) {
|
||||
_call.callDone();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::sendCode() {
|
||||
if (_sendCodeRequestId) {
|
||||
return;
|
||||
}
|
||||
const auto code = _code->getDigitsOnly();
|
||||
if (code.isEmpty()) {
|
||||
_code->showError();
|
||||
return;
|
||||
}
|
||||
|
||||
_code->setDisabled(true);
|
||||
setFocus();
|
||||
|
||||
showError(QString());
|
||||
|
||||
_sendCodeRequestId = _api.request(MTPaccount_ConfirmPhone(
|
||||
MTP_string(_phoneHash),
|
||||
MTP_string(code)
|
||||
)).done([=](const MTPBool &result) {
|
||||
confirmDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
confirmFail(error);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::confirmDone(const MTPBool &result) {
|
||||
_sendCodeRequestId = 0;
|
||||
Ui::show(Box<InformBox>(tr::lng_confirm_phone_success(tr::now, lt_phone, Ui::FormatPhone(_phone))));
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::confirmFail(const MTP::Error &error) {
|
||||
auto errorText = Lang::Hard::ServerError();
|
||||
if (MTP::IsFloodError(error)) {
|
||||
errorText = tr::lng_flood_error(tr::now);
|
||||
} else {
|
||||
auto &errorType = error.type();
|
||||
if (errorType == qstr("PHONE_CODE_EMPTY") || errorType == qstr("PHONE_CODE_INVALID")) {
|
||||
errorText = tr::lng_bad_code(tr::now);
|
||||
}
|
||||
}
|
||||
_sendCodeRequestId = 0;
|
||||
_code->setDisabled(false);
|
||||
_code->setFocus();
|
||||
showError(errorText);
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::showError(const QString &error) {
|
||||
_error = error;
|
||||
if (!_error.isEmpty()) {
|
||||
_code->showError();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
p.setFont(st::boxTextFont);
|
||||
auto callText = _call.getText();
|
||||
if (!callText.isEmpty()) {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
auto callTextRectLeft = st::usernamePadding.left();
|
||||
auto callTextRectTop = _about->y() + _about->height();
|
||||
auto callTextRectWidth = width() - 2 * st::usernamePadding.left();
|
||||
auto callTextRect = QRect(callTextRectLeft, callTextRectTop, callTextRectWidth, st::usernameSkip);
|
||||
p.drawText(callTextRect, callText, style::al_left);
|
||||
}
|
||||
auto errorText = _error;
|
||||
if (errorText.isEmpty()) {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
errorText = tr::lng_confirm_phone_enter_code(tr::now);
|
||||
} else {
|
||||
p.setPen(st::boxTextFgError);
|
||||
}
|
||||
auto errorTextRectLeft = st::usernamePadding.left();
|
||||
auto errorTextRectTop = _code->y() + _code->height();
|
||||
auto errorTextRectWidth = width() - 2 * st::usernamePadding.left();
|
||||
auto errorTextRect = QRect(errorTextRectLeft, errorTextRectTop, errorTextRectWidth, st::usernameSkip);
|
||||
p.drawText(errorTextRect, errorText, style::al_left);
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height());
|
||||
_code->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top());
|
||||
|
||||
_about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip);
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::setInnerFocus() {
|
||||
_code->setFocusFast();
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Ui {
|
||||
class InputField;
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
void ShowPhoneBannedError(const QString &phone);
|
||||
|
||||
class SentCodeField : public Ui::InputField {
|
||||
public:
|
||||
SentCodeField(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
rpl::producer<QString> placeholder = nullptr,
|
||||
const QString &val = QString());
|
||||
|
||||
void setAutoSubmit(int length, Fn<void()> submitCallback);
|
||||
void setChangedCallback(Fn<void()> changedCallback);
|
||||
QString getDigitsOnly() const;
|
||||
|
||||
private:
|
||||
void fix();
|
||||
|
||||
// Flag for not calling onTextChanged() recursively.
|
||||
bool _fixing = false;
|
||||
|
||||
int _autoSubmitLength = 0;
|
||||
Fn<void()> _submitCallback;
|
||||
Fn<void()> _changedCallback;
|
||||
|
||||
};
|
||||
|
||||
class SentCodeCall {
|
||||
public:
|
||||
SentCodeCall(
|
||||
FnMut<void()> callCallback,
|
||||
Fn<void()> updateCallback);
|
||||
|
||||
enum class State {
|
||||
Waiting,
|
||||
Calling,
|
||||
Called,
|
||||
Disabled,
|
||||
};
|
||||
struct Status {
|
||||
Status() {
|
||||
}
|
||||
Status(State state, int timeout) : state(state), timeout(timeout) {
|
||||
}
|
||||
|
||||
State state = State::Disabled;
|
||||
int timeout = 0;
|
||||
};
|
||||
void setStatus(const Status &status);
|
||||
|
||||
void callDone() {
|
||||
if (_status.state == State::Calling) {
|
||||
_status.state = State::Called;
|
||||
if (_update) {
|
||||
_update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString getText() const;
|
||||
|
||||
private:
|
||||
Status _status;
|
||||
base::Timer _timer;
|
||||
FnMut<void()> _call;
|
||||
Fn<void()> _update;
|
||||
|
||||
};
|
||||
|
||||
class ConfirmPhoneBox final : public Ui::BoxContent {
|
||||
public:
|
||||
static void Start(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &phone,
|
||||
const QString &hash);
|
||||
|
||||
[[nodiscard]] Main::Session &session() const {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
ConfirmPhoneBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
const QString &phone,
|
||||
const QString &hash);
|
||||
friend class object_ptr<ConfirmPhoneBox>;
|
||||
|
||||
void sendCode();
|
||||
void sendCall();
|
||||
void checkPhoneAndHash();
|
||||
|
||||
void sendCodeDone(const MTPauth_SentCode &result);
|
||||
void sendCodeFail(const MTP::Error &error);
|
||||
|
||||
void callDone(const MTPauth_SentCode &result);
|
||||
|
||||
void confirmDone(const MTPBool &result);
|
||||
void confirmFail(const MTP::Error &error);
|
||||
|
||||
QString getPhone() const {
|
||||
return _phone;
|
||||
}
|
||||
void launch();
|
||||
|
||||
void showError(const QString &error);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _sendCodeRequestId = 0;
|
||||
|
||||
// _hash from the link for account.sendConfirmPhoneCode call.
|
||||
// _phoneHash from auth.sentCode for account.confirmPhone call.
|
||||
QString _phone, _hash;
|
||||
QString _phoneHash;
|
||||
|
||||
// If we receive the code length, we autosubmit _code field when enough symbols is typed.
|
||||
int _sentCodeLength = 0;
|
||||
|
||||
mtpRequestId _checkCodeRequestId = 0;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _about = { nullptr };
|
||||
object_ptr<SentCodeField> _code = { nullptr };
|
||||
|
||||
QString _error;
|
||||
SentCodeCall _call;
|
||||
|
||||
};
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/connection_box.h"
|
||||
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qthelp_url.h"
|
||||
@@ -74,7 +74,7 @@ void HostInput::correctValue(
|
||||
QString newText;
|
||||
int newCursor = nowCursor;
|
||||
newText.reserve(now.size());
|
||||
for (auto i = 0, l = now.size(); i < l; ++i) {
|
||||
for (auto i = 0, l = int(now.size()); i < l; ++i) {
|
||||
if (now[i] == ',') {
|
||||
newText.append('.');
|
||||
} else {
|
||||
@@ -120,7 +120,7 @@ void Base64UrlInput::correctValue(
|
||||
QString newText;
|
||||
newText.reserve(now.size());
|
||||
auto newPos = nowCursor;
|
||||
for (auto i = 0, l = now.size(); i < l; ++i) {
|
||||
for (auto i = 0, l = int(now.size()); i < l; ++i) {
|
||||
const auto ch = now[i];
|
||||
if ((ch >= '0' && ch <= '9')
|
||||
|| (ch >= 'a' && ch <= 'z')
|
||||
@@ -537,8 +537,8 @@ void ProxyRow::showMenu() {
|
||||
_deleteClicks.fire({});
|
||||
});
|
||||
}
|
||||
const auto parentTopLeft = window()->mapToGlobal({ 0, 0 });
|
||||
const auto buttonTopLeft = _menuToggle->mapToGlobal({ 0, 0 });
|
||||
const auto parentTopLeft = window()->mapToGlobal(QPoint());
|
||||
const auto buttonTopLeft = _menuToggle->mapToGlobal(QPoint());
|
||||
const auto parent = QRect(parentTopLeft, window()->size());
|
||||
const auto button = QRect(buttonTopLeft, _menuToggle->size());
|
||||
const auto bottom = button.y()
|
||||
@@ -1131,13 +1131,13 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
close();
|
||||
};
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
tr::lng_sure_enable(tr::now),
|
||||
std::move(callback)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
(proxy.status() == ProxyData::Status::Unsupported
|
||||
? tr::lng_proxy_unsupported(tr::now)
|
||||
: tr::lng_proxy_invalid(tr::now))));
|
||||
|
||||
459
Telegram/SourceFiles/boxes/delete_messages_box.cpp
Normal file
459
Telegram/SourceFiles/boxes/delete_messages_box.cpp
Normal file
@@ -0,0 +1,459 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/delete_messages_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/controls/history_view_ttl_button.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "facades.h" // Ui::showChatsList
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<HistoryItem*> item,
|
||||
bool suggestModerateActions)
|
||||
: _session(&item->history()->session())
|
||||
, _ids(1, item->fullId()) {
|
||||
if (suggestModerateActions) {
|
||||
_moderateBan = item->suggestBanReport();
|
||||
_moderateDeleteAll = item->suggestDeleteAllReport();
|
||||
if (_moderateBan || _moderateDeleteAll) {
|
||||
_moderateFrom = item->from()->asUser();
|
||||
_moderateInChannel = item->history()->peer->asChannel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
MessageIdsList &&selected)
|
||||
: _session(session)
|
||||
, _ids(std::move(selected)) {
|
||||
Expects(!_ids.empty());
|
||||
}
|
||||
|
||||
DeleteMessagesBox::DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear)
|
||||
: _session(&peer->session())
|
||||
, _wipeHistoryPeer(peer)
|
||||
, _wipeHistoryJustClear(justClear) {
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::prepare() {
|
||||
auto details = TextWithEntities();
|
||||
const auto appendDetails = [&](TextWithEntities &&text) {
|
||||
details.append(qstr("\n\n")).append(std::move(text));
|
||||
};
|
||||
auto deleteText = lifetime().make_state<rpl::variable<QString>>();
|
||||
*deleteText = tr::lng_box_delete();
|
||||
auto deleteStyle = &st::defaultBoxButton;
|
||||
auto canDelete = true;
|
||||
if (const auto peer = _wipeHistoryPeer) {
|
||||
if (_wipeHistoryJustClear) {
|
||||
const auto isChannel = peer->isBroadcast();
|
||||
const auto isPublicGroup = peer->isMegagroup()
|
||||
&& peer->asChannel()->isPublic();
|
||||
if (isChannel || isPublicGroup) {
|
||||
canDelete = false;
|
||||
}
|
||||
details.text = isChannel
|
||||
? tr::lng_no_clear_history_channel(tr::now)
|
||||
: isPublicGroup
|
||||
? tr::lng_no_clear_history_group(tr::now)
|
||||
: peer->isSelf()
|
||||
? tr::lng_sure_delete_saved_messages(tr::now)
|
||||
: peer->isUser()
|
||||
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
|
||||
: tr::lng_sure_delete_group_history(
|
||||
tr::now,
|
||||
lt_group,
|
||||
peer->name);
|
||||
deleteStyle = &st::attentionBoxButton;
|
||||
} else {
|
||||
details.text = peer->isSelf()
|
||||
? tr::lng_sure_delete_saved_messages(tr::now)
|
||||
: peer->isUser()
|
||||
? tr::lng_sure_delete_history(tr::now, lt_contact, peer->name)
|
||||
: peer->isChat()
|
||||
? tr::lng_sure_delete_and_exit(tr::now, lt_group, peer->name)
|
||||
: peer->isMegagroup()
|
||||
? tr::lng_sure_leave_group(tr::now)
|
||||
: tr::lng_sure_leave_channel(tr::now);
|
||||
if (!peer->isUser()) {
|
||||
*deleteText = tr::lng_box_leave();
|
||||
}
|
||||
deleteStyle = &st::attentionBoxButton;
|
||||
}
|
||||
if (auto revoke = revokeText(peer)) {
|
||||
_revoke.create(
|
||||
this,
|
||||
revoke->checkbox,
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
appendDetails(std::move(revoke->description));
|
||||
if (!peer->isUser() && !_wipeHistoryJustClear) {
|
||||
_revoke->checkedValue(
|
||||
) | rpl::start_with_next([=](bool revokeForAll) {
|
||||
*deleteText = revokeForAll
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_box_leave();
|
||||
}, _revoke->lifetime());
|
||||
}
|
||||
}
|
||||
} else if (_moderateFrom) {
|
||||
Assert(_moderateInChannel != nullptr);
|
||||
|
||||
details.text = tr::lng_selected_delete_sure_this(tr::now);
|
||||
if (_moderateBan) {
|
||||
_banUser.create(
|
||||
this,
|
||||
tr::lng_ban_user(tr::now),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
}
|
||||
_reportSpam.create(
|
||||
this,
|
||||
tr::lng_report_spam(tr::now),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
if (_moderateDeleteAll) {
|
||||
_deleteAll.create(
|
||||
this,
|
||||
tr::lng_delete_all_from(tr::now),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
}
|
||||
} else {
|
||||
details.text = (_ids.size() == 1)
|
||||
? tr::lng_selected_delete_sure_this(tr::now)
|
||||
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
|
||||
if (const auto peer = checkFromSinglePeer()) {
|
||||
auto count = int(_ids.size());
|
||||
if (hasScheduledMessages()) {
|
||||
} else if (auto revoke = revokeText(peer)) {
|
||||
_revoke.create(
|
||||
this,
|
||||
revoke->checkbox,
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
appendDetails(std::move(revoke->description));
|
||||
} else if (peer->isChannel()) {
|
||||
if (peer->isMegagroup()) {
|
||||
appendDetails({
|
||||
tr::lng_delete_for_everyone_hint(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count)
|
||||
});
|
||||
}
|
||||
} else if (peer->isChat()) {
|
||||
appendDetails({
|
||||
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
|
||||
});
|
||||
} else if (!peer->isSelf()) {
|
||||
appendDetails({
|
||||
tr::lng_delete_for_me_hint(tr::now, lt_count, count)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
|
||||
|
||||
if (_wipeHistoryJustClear
|
||||
&& _wipeHistoryPeer
|
||||
&& ((_wipeHistoryPeer->isUser()
|
||||
&& !_wipeHistoryPeer->isSelf()
|
||||
&& !_wipeHistoryPeer->isNotificationsUser())
|
||||
|| (_wipeHistoryPeer->isChat()
|
||||
&& _wipeHistoryPeer->asChat()->canDeleteMessages())
|
||||
|| (_wipeHistoryPeer->isChannel()
|
||||
&& _wipeHistoryPeer->asChannel()->canDeleteMessages()))) {
|
||||
_wipeHistoryPeer->updateFull();
|
||||
_autoDeleteSettings.create(
|
||||
this,
|
||||
(_wipeHistoryPeer->messagesTTL()
|
||||
? tr::lng_edit_auto_delete_settings(tr::now)
|
||||
: tr::lng_enable_auto_delete(tr::now)),
|
||||
st::boxLinkButton);
|
||||
_autoDeleteSettings->setClickedCallback([=] {
|
||||
getDelegate()->show(
|
||||
Box(
|
||||
HistoryView::Controls::AutoDeleteSettingsBox,
|
||||
_wipeHistoryPeer),
|
||||
Ui::LayerOption(0));
|
||||
});
|
||||
}
|
||||
|
||||
if (canDelete) {
|
||||
addButton(
|
||||
deleteText->value(),
|
||||
[=] { deleteAndClear(); },
|
||||
*deleteStyle);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
} else {
|
||||
addButton(tr::lng_about_done(), [=] { closeBox(); });
|
||||
}
|
||||
|
||||
auto fullHeight = st::boxPadding.top()
|
||||
+ _text->height()
|
||||
+ st::boxPadding.bottom();
|
||||
if (_moderateFrom) {
|
||||
fullHeight += st::boxMediumSkip;
|
||||
if (_banUser) {
|
||||
fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
fullHeight += _reportSpam->heightNoMargins();
|
||||
if (_deleteAll) {
|
||||
fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
|
||||
}
|
||||
} else if (_revoke) {
|
||||
fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
|
||||
}
|
||||
if (_autoDeleteSettings) {
|
||||
fullHeight += st::boxMediumSkip
|
||||
+ _autoDeleteSettings->height()
|
||||
+ st::boxLittleSkip;
|
||||
}
|
||||
setDimensions(st::boxWidth, fullHeight);
|
||||
}
|
||||
|
||||
bool DeleteMessagesBox::hasScheduledMessages() const {
|
||||
for (const auto &fullId : _ids) {
|
||||
if (const auto item = _session->data().message(fullId)) {
|
||||
if (item->isScheduled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
|
||||
auto result = (PeerData*)nullptr;
|
||||
for (const auto &fullId : _ids) {
|
||||
if (const auto item = _session->data().message(fullId)) {
|
||||
const auto peer = item->history()->peer;
|
||||
if (!result) {
|
||||
result = peer;
|
||||
} else if (result != peer) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
|
||||
-> std::optional<RevokeConfig> {
|
||||
auto result = RevokeConfig();
|
||||
if (peer == _wipeHistoryPeer) {
|
||||
if (!peer->canRevokeFullHistory()) {
|
||||
return std::nullopt;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
result.checkbox = tr::lng_delete_for_other_check(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->firstName);
|
||||
} else if (_wipeHistoryJustClear) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto items = ranges::views::all(
|
||||
_ids
|
||||
) | ranges::views::transform([&](FullMsgId id) {
|
||||
return peer->owner().message(id);
|
||||
}) | ranges::views::filter([](HistoryItem *item) {
|
||||
return (item != nullptr);
|
||||
}) | ranges::to_vector;
|
||||
|
||||
if (items.size() != _ids.size()) {
|
||||
// We don't have information about all messages.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto canRevoke = [&](HistoryItem * item) {
|
||||
return item->canDeleteForEveryone(now);
|
||||
};
|
||||
const auto cannotRevoke = [&](HistoryItem *item) {
|
||||
return !item->canDeleteForEveryone(now);
|
||||
};
|
||||
const auto canRevokeAll = ranges::none_of(items, cannotRevoke);
|
||||
auto outgoing = items | ranges::views::filter(&HistoryItem::out);
|
||||
const auto canRevokeOutgoingCount = canRevokeAll
|
||||
? -1
|
||||
: ranges::count_if(outgoing, canRevoke);
|
||||
|
||||
if (canRevokeAll) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
result.checkbox = tr::lng_delete_for_other_check(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->firstName);
|
||||
} else {
|
||||
result.checkbox = tr::lng_delete_for_everyone_check(tr::now);
|
||||
}
|
||||
return result;
|
||||
} else if (canRevokeOutgoingCount > 0) {
|
||||
result.checkbox = tr::lng_delete_for_other_my(tr::now);
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (canRevokeOutgoingCount == 1) {
|
||||
result.description = tr::lng_selected_unsend_about_user_one(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->shortName()),
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.description = tr::lng_selected_unsend_about_user(
|
||||
tr::now,
|
||||
lt_count,
|
||||
canRevokeOutgoingCount,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->shortName()),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
} else if (canRevokeOutgoingCount == 1) {
|
||||
result.description = tr::lng_selected_unsend_about_group_one(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.description = tr::lng_selected_unsend_about_group(
|
||||
tr::now,
|
||||
lt_count,
|
||||
canRevokeOutgoingCount,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
|
||||
auto top = _text->bottomNoMargins() + st::boxMediumSkip;
|
||||
if (_moderateFrom) {
|
||||
if (_banUser) {
|
||||
_banUser->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _banUser->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
_reportSpam->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _reportSpam->heightNoMargins() + st::boxLittleSkip;
|
||||
if (_deleteAll) {
|
||||
_deleteAll->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _deleteAll->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
} else if (_revoke) {
|
||||
const auto availableWidth = width() - 2 * st::boxPadding.left();
|
||||
_revoke->resizeToNaturalWidth(availableWidth);
|
||||
_revoke->moveToLeft(st::boxPadding.left(), top);
|
||||
top += _revoke->heightNoMargins() + st::boxLittleSkip;
|
||||
}
|
||||
if (_autoDeleteSettings) {
|
||||
top += st::boxMediumSkip - st::boxLittleSkip;
|
||||
_autoDeleteSettings->moveToLeft(st::boxPadding.left(), top);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
// Don't make the clearing history so easy.
|
||||
if (!_wipeHistoryPeer) {
|
||||
deleteAndClear();
|
||||
}
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteMessagesBox::deleteAndClear() {
|
||||
const auto revoke = _revoke ? _revoke->checked() : false;
|
||||
if (const auto peer = _wipeHistoryPeer) {
|
||||
const auto justClear = _wipeHistoryJustClear;
|
||||
closeBox();
|
||||
|
||||
if (justClear) {
|
||||
peer->session().api().clearHistory(peer, revoke);
|
||||
} else {
|
||||
for (const auto &controller : peer->session().windows()) {
|
||||
if (controller->activeChatCurrent().peer() == peer) {
|
||||
Ui::showChatsList(&peer->session());
|
||||
}
|
||||
}
|
||||
// Don't delete old history by default,
|
||||
// because Android app doesn't.
|
||||
//
|
||||
//if (const auto from = peer->migrateFrom()) {
|
||||
// peer->session().api().deleteConversation(from, false);
|
||||
//}
|
||||
peer->session().api().deleteConversation(peer, revoke);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_moderateFrom) {
|
||||
if (_banUser && _banUser->checked()) {
|
||||
_moderateInChannel->session().api().kickParticipant(
|
||||
_moderateInChannel,
|
||||
_moderateFrom,
|
||||
ChatRestrictionsInfo());
|
||||
}
|
||||
if (_reportSpam->checked()) {
|
||||
_moderateInChannel->session().api().request(
|
||||
MTPchannels_ReportSpam(
|
||||
_moderateInChannel->inputChannel,
|
||||
_moderateFrom->inputUser,
|
||||
MTP_vector<MTPint>(1, MTP_int(_ids[0].msg)))
|
||||
).send();
|
||||
}
|
||||
if (_deleteAll && _deleteAll->checked()) {
|
||||
_moderateInChannel->session().api().deleteAllFromUser(
|
||||
_moderateInChannel,
|
||||
_moderateFrom);
|
||||
}
|
||||
}
|
||||
|
||||
if (_deleteConfirmedCallback) {
|
||||
_deleteConfirmedCallback();
|
||||
}
|
||||
|
||||
// deleteMessages can initiate closing of the current section,
|
||||
// which will cause this box to be destroyed.
|
||||
const auto session = _session;
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
|
||||
session->data().histories().deleteMessages(_ids, revoke);
|
||||
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
session->data().sendHistoryChangeNotifications();
|
||||
}
|
||||
74
Telegram/SourceFiles/boxes/delete_messages_box.h
Normal file
74
Telegram/SourceFiles/boxes/delete_messages_box.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class Checkbox;
|
||||
class FlatLabel;
|
||||
class LinkButton;
|
||||
} // namespace Ui
|
||||
|
||||
class DeleteMessagesBox final : public Ui::BoxContent {
|
||||
public:
|
||||
DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<HistoryItem*> item,
|
||||
bool suggestModerateActions);
|
||||
DeleteMessagesBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
MessageIdsList &&selected);
|
||||
DeleteMessagesBox(QWidget*, not_null<PeerData*> peer, bool justClear);
|
||||
|
||||
void setDeleteConfirmedCallback(Fn<void()> callback) {
|
||||
_deleteConfirmedCallback = std::move(callback);
|
||||
}
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
struct RevokeConfig {
|
||||
QString checkbox;
|
||||
TextWithEntities description;
|
||||
};
|
||||
void deleteAndClear();
|
||||
[[nodiscard]] PeerData *checkFromSinglePeer() const;
|
||||
[[nodiscard]] bool hasScheduledMessages() const;
|
||||
[[nodiscard]] std::optional<RevokeConfig> revokeText(
|
||||
not_null<PeerData*> peer) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
PeerData * const _wipeHistoryPeer = nullptr;
|
||||
const bool _wipeHistoryJustClear = false;
|
||||
const MessageIdsList _ids;
|
||||
UserData *_moderateFrom = nullptr;
|
||||
ChannelData *_moderateInChannel = nullptr;
|
||||
bool _moderateBan = false;
|
||||
bool _moderateDeleteAll = false;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _text = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _revoke = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _banUser = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _reportSpam = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _deleteAll = { nullptr };
|
||||
object_ptr<Ui::LinkButton> _autoDeleteSettings = { nullptr };
|
||||
|
||||
Fn<void()> _deleteConfirmedCallback;
|
||||
|
||||
};
|
||||
@@ -39,11 +39,6 @@ using DictState = BlobState;
|
||||
using QueryCallback = Fn<void(const QString &)>;
|
||||
constexpr auto kMaxQueryLength = 15;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
#define OLD_QT
|
||||
using QStringView = QString;
|
||||
#endif
|
||||
|
||||
class Inner : public Ui::RpWidget {
|
||||
public:
|
||||
Inner(
|
||||
|
||||
@@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "core/application.h"
|
||||
@@ -478,7 +478,7 @@ void Rows::showMenu(int index) {
|
||||
}
|
||||
}
|
||||
const auto toggle = menuToggleArea(row);
|
||||
const auto parentTopLeft = window()->mapToGlobal({ 0, 0 });
|
||||
const auto parentTopLeft = window()->mapToGlobal(QPoint());
|
||||
const auto buttonTopLeft = mapToGlobal(toggle.topLeft());
|
||||
const auto parent = QRect(parentTopLeft, window()->size());
|
||||
const auto button = QRect(buttonTopLeft, toggle.size());
|
||||
|
||||
152
Telegram/SourceFiles/boxes/max_invite_box.cpp
Normal file
152
Telegram/SourceFiles/boxes/max_invite_box.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/max_invite_box.h"
|
||||
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace {
|
||||
|
||||
TextParseOptions kInformBoxTextOptions = {
|
||||
(TextParseLinks
|
||||
| TextParseMultiline
|
||||
| TextParseMarkdown
|
||||
| TextParseRichText), // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
MaxInviteBox::MaxInviteBox(QWidget*, not_null<ChannelData*> channel)
|
||||
: BoxContent()
|
||||
, _channel(channel)
|
||||
, _text(
|
||||
st::boxLabelStyle,
|
||||
tr::lng_participant_invite_sorry(
|
||||
tr::now,
|
||||
lt_count,
|
||||
channel->session().serverConfig().chatSizeMax),
|
||||
kInformBoxTextOptions,
|
||||
(st::boxWidth
|
||||
- st::boxPadding.left()
|
||||
- st::defaultBox.buttonPadding.right())) {
|
||||
}
|
||||
|
||||
void MaxInviteBox::prepare() {
|
||||
setMouseTracking(true);
|
||||
|
||||
addButton(tr::lng_box_ok(), [=] { closeBox(); });
|
||||
|
||||
_textWidth = st::boxWidth
|
||||
- st::boxPadding.left()
|
||||
- st::defaultBox.buttonPadding.right();
|
||||
_textHeight = std::min(
|
||||
_text.countHeight(_textWidth),
|
||||
16 * st::boxLabelStyle.lineHeight);
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
st::boxPadding.top()
|
||||
+ _textHeight
|
||||
+ st::boxTextFont->height
|
||||
+ st::boxTextFont->height * 2
|
||||
+ st::newGroupLinkPadding.bottom());
|
||||
|
||||
if (_channel->inviteLink().isEmpty()) {
|
||||
_channel->session().api().requestFullPeer(_channel);
|
||||
}
|
||||
_channel->session().changes().peerUpdates(
|
||||
_channel,
|
||||
Data::PeerUpdate::Flag::InviteLinks
|
||||
) | rpl::start_with_next([=] {
|
||||
rtlupdate(_invitationLink);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void MaxInviteBox::mouseMoveEvent(QMouseEvent *e) {
|
||||
updateSelected(e->globalPos());
|
||||
}
|
||||
|
||||
void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
|
||||
mouseMoveEvent(e);
|
||||
if (_linkOver) {
|
||||
if (!_channel->inviteLink().isEmpty()) {
|
||||
QGuiApplication::clipboard()->setText(_channel->inviteLink());
|
||||
Ui::Toast::Show(tr::lng_create_channel_link_copied(tr::now));
|
||||
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
|
||||
_creatingInviteLink = true;
|
||||
_channel->session().api().inviteLinks().create(_channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaxInviteBox::leaveEventHook(QEvent *e) {
|
||||
updateSelected(QCursor::pos());
|
||||
}
|
||||
|
||||
void MaxInviteBox::updateSelected(const QPoint &cursorGlobalPosition) {
|
||||
const auto p = QPoint(mapFromGlobal(cursorGlobalPosition));
|
||||
|
||||
const auto linkOver = _invitationLink.contains(p);
|
||||
if (linkOver != _linkOver) {
|
||||
_linkOver = linkOver;
|
||||
update();
|
||||
setCursor(_linkOver ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
}
|
||||
|
||||
void MaxInviteBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
Painter p(this);
|
||||
|
||||
// draw box title / text
|
||||
p.setPen(st::boxTextFg);
|
||||
_text.drawLeftElided(
|
||||
p,
|
||||
st::boxPadding.left(),
|
||||
st::boxPadding.top(),
|
||||
_textWidth,
|
||||
width(),
|
||||
16,
|
||||
style::al_left);
|
||||
|
||||
auto option = QTextOption(style::al_left);
|
||||
option.setWrapMode(QTextOption::WrapAnywhere);
|
||||
p.setFont(_linkOver
|
||||
? st::defaultInputField.font->underline()
|
||||
: st::defaultInputField.font);
|
||||
p.setPen(st::defaultLinkButton.color);
|
||||
const auto inviteLinkText = _channel->inviteLink().isEmpty()
|
||||
? tr::lng_group_invite_create(tr::now)
|
||||
: _channel->inviteLink();
|
||||
p.drawText(_invitationLink, inviteLinkText, option);
|
||||
}
|
||||
|
||||
void MaxInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_invitationLink = myrtlrect(
|
||||
st::boxPadding.left(),
|
||||
st::boxPadding.top() + _textHeight + st::boxTextFont->height,
|
||||
width() - st::boxPadding.left() - st::boxPadding.right(),
|
||||
2 * st::boxTextFont->height);
|
||||
}
|
||||
39
Telegram/SourceFiles/boxes/max_invite_box.h
Normal file
39
Telegram/SourceFiles/boxes/max_invite_box.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
class MaxInviteBox final : public Ui::BoxContent {
|
||||
public:
|
||||
MaxInviteBox(QWidget*, not_null<ChannelData*> channel);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateSelected(const QPoint &cursorGlobalPosition);
|
||||
|
||||
not_null<ChannelData*> _channel;
|
||||
|
||||
Ui::Text::String _text;
|
||||
int32 _textWidth, _textHeight;
|
||||
|
||||
QRect _invitationLink;
|
||||
bool _linkOver = false;
|
||||
bool _creatingInviteLink = false;
|
||||
|
||||
QPoint _lastMousePos;
|
||||
|
||||
};
|
||||
@@ -9,8 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/confirm_phone_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -24,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/sent_code_field.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "base/qt_adapters.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -107,7 +108,7 @@ void StartPendingReset(
|
||||
auto finish = [=](const QString &message) mutable {
|
||||
if (const auto strong = weak.data()) {
|
||||
if (!message.isEmpty()) {
|
||||
strong->getDelegate()->show(Box<InformBox>(message));
|
||||
strong->getDelegate()->show(Box<Ui::InformBox>(message));
|
||||
}
|
||||
strong->closeBox();
|
||||
}
|
||||
@@ -138,7 +139,7 @@ void StartPendingReset(
|
||||
lt_count,
|
||||
minutes);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->getDelegate()->show(Box<InformBox>(
|
||||
strong->getDelegate()->show(Box<Ui::InformBox>(
|
||||
tr::lng_cloud_password_reset_later(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
@@ -440,7 +441,7 @@ void PasscodeBox::recoverPasswordDone(
|
||||
if (weak) {
|
||||
_newPasswordSet.fire_copy(newPasswordBytes);
|
||||
if (weak) {
|
||||
getDelegate()->show(Box<InformBox>(
|
||||
getDelegate()->show(Box<Ui::InformBox>(
|
||||
tr::lng_cloud_password_updated(tr::now)));
|
||||
if (weak) {
|
||||
closeBox();
|
||||
@@ -462,7 +463,7 @@ void PasscodeBox::setPasswordDone(const QByteArray &newPasswordBytes) {
|
||||
: _oldPasscode->isHidden()
|
||||
? tr::lng_cloud_password_was_set(tr::now)
|
||||
: tr::lng_cloud_password_updated(tr::now);
|
||||
getDelegate()->show(Box<InformBox>(text));
|
||||
getDelegate()->show(Box<Ui::InformBox>(text));
|
||||
if (weak) {
|
||||
closeBox();
|
||||
}
|
||||
@@ -525,7 +526,7 @@ void PasscodeBox::setPasswordFail(
|
||||
const MTP::Error &error) {
|
||||
const auto prefix = qstr("EMAIL_UNCONFIRMED_");
|
||||
if (error.type().startsWith(prefix)) {
|
||||
const auto codeLength = error.type().midRef(prefix.size()).toInt();
|
||||
const auto codeLength = base::StringViewMid(error.type(), prefix.size()).toInt();
|
||||
|
||||
closeReplacedBy();
|
||||
_setRequest = 0;
|
||||
@@ -562,7 +563,7 @@ void PasscodeBox::validateEmail(
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_clearUnconfirmedPassword.fire({});
|
||||
if (weak) {
|
||||
auto box = Box<InformBox>(
|
||||
auto box = Box<Ui::InformBox>(
|
||||
Lang::Hard::EmailConfirmationExpired());
|
||||
weak->getDelegate()->show(
|
||||
std::move(box),
|
||||
@@ -689,7 +690,7 @@ void PasscodeBox::save(bool force) {
|
||||
if (!onlyCheck && !_recoverEmail->isHidden() && email.isEmpty() && !force) {
|
||||
_skipEmailWarning = true;
|
||||
_replacedBy = getDelegate()->show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_cloud_password_about_recover(tr::now),
|
||||
tr::lng_cloud_password_skip_email(tr::now),
|
||||
st::attentionBoxButton,
|
||||
@@ -726,7 +727,7 @@ void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
|
||||
send();
|
||||
close();
|
||||
};
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
tr::lng_cloud_password_passport_losing(tr::now),
|
||||
tr::lng_continue(tr::now),
|
||||
confirmed));
|
||||
@@ -797,7 +798,7 @@ void PasscodeBox::requestPasswordData() {
|
||||
}
|
||||
|
||||
void PasscodeBox::serverError() {
|
||||
getDelegate()->show(Box<InformBox>(Lang::Hard::ServerError()));
|
||||
getDelegate()->show(Box<Ui::InformBox>(Lang::Hard::ServerError()));
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -952,7 +953,7 @@ void PasscodeBox::suggestSecretReset(const QString &newPassword) {
|
||||
resetSecret(check, newPassword, std::move(close));
|
||||
});
|
||||
};
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
Lang::Hard::PassportCorruptedChange(),
|
||||
Lang::Hard::PassportCorruptedReset(),
|
||||
std::move(resetSecretAndSave)));
|
||||
@@ -1085,7 +1086,7 @@ void PasscodeBox::recoverByEmail() {
|
||||
}
|
||||
});
|
||||
});
|
||||
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
|
||||
*confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
tr::lng_cloud_password_reset_no_email(tr::now),
|
||||
tr::lng_cloud_password_reset_ok(tr::now),
|
||||
reset));
|
||||
@@ -1170,7 +1171,7 @@ RecoverBox::RecoverBox(
|
||||
}
|
||||
});
|
||||
});
|
||||
*confirmBox = getDelegate()->show(Box<ConfirmBox>(
|
||||
*confirmBox = getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
tr::lng_cloud_password_reset_with_email(tr::now),
|
||||
tr::lng_cloud_password_reset_ok(tr::now),
|
||||
reset));
|
||||
@@ -1272,7 +1273,7 @@ void RecoverBox::submit() {
|
||||
send();
|
||||
close();
|
||||
};
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
tr::lng_cloud_password_passport_losing(tr::now),
|
||||
tr::lng_continue(tr::now),
|
||||
confirmed));
|
||||
@@ -1297,7 +1298,7 @@ void RecoverBox::proceedToClear() {
|
||||
_submitRequest = 0;
|
||||
_newPasswordSet.fire({});
|
||||
getDelegate()->show(
|
||||
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
|
||||
Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
@@ -1345,7 +1346,7 @@ void RecoverBox::checkSubmitFail(const MTP::Error &error) {
|
||||
if (err == qstr("PASSWORD_EMPTY")) {
|
||||
_newPasswordSet.fire(QByteArray());
|
||||
getDelegate()->show(
|
||||
Box<InformBox>(tr::lng_cloud_password_removed(tr::now)),
|
||||
Box<Ui::InformBox>(tr::lng_cloud_password_removed(tr::now)),
|
||||
Ui::LayerOption::CloseOther);
|
||||
} else if (err == qstr("PASSWORD_RECOVERY_NA")) {
|
||||
closeBox();
|
||||
@@ -1386,7 +1387,8 @@ RecoveryEmailValidation ConfirmRecoveryEmail(
|
||||
reloads->fire({});
|
||||
if (*weak) {
|
||||
(*weak)->getDelegate()->show(
|
||||
Box<InformBox>(tr::lng_cloud_password_was_set(tr::now)),
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_cloud_password_was_set(tr::now)),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
@@ -1398,7 +1400,7 @@ RecoveryEmailValidation ConfirmRecoveryEmail(
|
||||
} else if (error.type() == qstr("EMAIL_HASH_EXPIRED")) {
|
||||
cancels->fire({});
|
||||
if (*weak) {
|
||||
auto box = Box<InformBox>(
|
||||
auto box = Box<Ui::InformBox>(
|
||||
Lang::Hard::EmailConfirmationExpired());
|
||||
(*weak)->getDelegate()->show(
|
||||
std::move(box),
|
||||
|
||||
@@ -270,6 +270,7 @@ bool PeerListController::hasComplexSearch() const {
|
||||
|
||||
void PeerListController::search(const QString &query) {
|
||||
Expects(hasComplexSearch());
|
||||
|
||||
_searchController->searchQuery(query);
|
||||
}
|
||||
|
||||
@@ -518,6 +519,63 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
int PeerListRow::elementsCount() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
QRect PeerListRow::elementGeometry(int element, int outerWidth) const {
|
||||
if (element != 1) {
|
||||
return QRect();
|
||||
}
|
||||
const auto size = rightActionSize();
|
||||
if (size.isEmpty()) {
|
||||
return QRect();
|
||||
}
|
||||
const auto margins = rightActionMargins();
|
||||
const auto right = margins.right();
|
||||
const auto top = margins.top();
|
||||
const auto left = outerWidth - right - size.width();
|
||||
return QRect(QPoint(left, top), size);
|
||||
}
|
||||
|
||||
bool PeerListRow::elementDisabled(int element) const {
|
||||
return (element == 1) && rightActionDisabled();
|
||||
}
|
||||
|
||||
bool PeerListRow::elementOnlySelect(int element) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void PeerListRow::elementAddRipple(
|
||||
int element,
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
if (element == 1) {
|
||||
rightActionAddRipple(point, std::move(updateCallback));
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListRow::elementsStopLastRipple() {
|
||||
rightActionStopLastRipple();
|
||||
}
|
||||
|
||||
void PeerListRow::elementsPaint(
|
||||
Painter &p,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
int selectedElement) {
|
||||
const auto geometry = elementGeometry(1, outerWidth);
|
||||
if (!geometry.isEmpty()) {
|
||||
rightActionPaint(
|
||||
p,
|
||||
geometry.x(),
|
||||
geometry.y(),
|
||||
outerWidth,
|
||||
selected,
|
||||
(selectedElement == 1));
|
||||
}
|
||||
}
|
||||
|
||||
QString PeerListRow::generateName() {
|
||||
return peer()->name;
|
||||
}
|
||||
@@ -1240,7 +1298,7 @@ int PeerListContent::resizeGetHeight(int newWidth) {
|
||||
return belowTop + _belowHeight;
|
||||
}
|
||||
|
||||
void PeerListContent::enterEventHook(QEvent *e) {
|
||||
void PeerListContent::enterEventHook(QEnterEvent *e) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
@@ -1272,11 +1330,16 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
auto updateCallback = [this, row, hint = _selected.index] {
|
||||
updateRow(row, hint);
|
||||
};
|
||||
if (_selected.action) {
|
||||
auto actionRect = getActiveActionRect(row, _selected.index);
|
||||
if (!actionRect.isEmpty()) {
|
||||
auto point = mapFromGlobal(QCursor::pos()) - actionRect.topLeft();
|
||||
row->addActionRipple(point, std::move(updateCallback));
|
||||
if (_selected.element) {
|
||||
const auto elementRect = getElementRect(
|
||||
row,
|
||||
_selected.index,
|
||||
_selected.element);
|
||||
if (!elementRect.isEmpty()) {
|
||||
row->elementAddRipple(
|
||||
_selected.element,
|
||||
mapFromGlobal(QCursor::pos()) - elementRect.topLeft(),
|
||||
std::move(updateCallback));
|
||||
}
|
||||
} else {
|
||||
auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
|
||||
@@ -1291,7 +1354,7 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anim::Disabled()) {
|
||||
if (anim::Disabled() && !_selected.element) {
|
||||
mousePressReleased(e->button());
|
||||
}
|
||||
}
|
||||
@@ -1308,8 +1371,8 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
setPressed(Selected());
|
||||
if (button == Qt::LeftButton && pressed == _selected) {
|
||||
if (auto row = getRow(pressed.index)) {
|
||||
if (pressed.action) {
|
||||
_controller->rowActionClicked(row);
|
||||
if (pressed.element) {
|
||||
_controller->rowElementClicked(row, pressed.element);
|
||||
} else {
|
||||
_controller->rowClicked(row);
|
||||
}
|
||||
@@ -1386,9 +1449,11 @@ void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
|
||||
void PeerListContent::setPressed(Selected pressed) {
|
||||
if (auto row = getRow(_pressed.index)) {
|
||||
if (_pressed == pressed) {
|
||||
return;
|
||||
} else if (const auto row = getRow(_pressed.index)) {
|
||||
row->stopLastRipple();
|
||||
row->stopLastActionRipple();
|
||||
row->elementsStopLastRipple();
|
||||
}
|
||||
_pressed = pressed;
|
||||
}
|
||||
@@ -1401,6 +1466,7 @@ crl::time PeerListContent::paintRow(
|
||||
Assert(row != nullptr);
|
||||
|
||||
row->lazyInitialize(_st.item);
|
||||
const auto outerWidth = width();
|
||||
|
||||
auto refreshStatusAt = row->refreshStatusTime();
|
||||
if (refreshStatusAt > 0 && now >= refreshStatusAt) {
|
||||
@@ -1417,8 +1483,8 @@ crl::time PeerListContent::paintRow(
|
||||
: (_pressed.index.value >= 0)
|
||||
? _pressed
|
||||
: _selected;
|
||||
const auto selected = (active.index == index);
|
||||
const auto actionSelected = (selected && active.action);
|
||||
const auto selected = (active.index == index)
|
||||
&& (!active.element || !row->elementOnlySelect(active.element));
|
||||
|
||||
if (_mode == Mode::Custom) {
|
||||
_controller->customRowPaint(p, now, row, selected);
|
||||
@@ -1428,27 +1494,29 @@ crl::time PeerListContent::paintRow(
|
||||
const auto &bg = selected
|
||||
? _st.item.button.textBgOver
|
||||
: _st.item.button.textBg;
|
||||
p.fillRect(0, 0, width(), _rowHeight, bg);
|
||||
row->paintRipple(p, 0, 0, width());
|
||||
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
|
||||
row->paintRipple(p, 0, 0, outerWidth);
|
||||
row->paintUserpic(
|
||||
p,
|
||||
_st.item,
|
||||
_st.item.photoPosition.x(),
|
||||
_st.item.photoPosition.y(),
|
||||
width());
|
||||
outerWidth);
|
||||
|
||||
p.setPen(st::contactsNameFg);
|
||||
|
||||
auto skipRight = _st.item.photoPosition.x();
|
||||
auto actionSize = row->actionSize();
|
||||
auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins();
|
||||
auto rightActionSize = row->rightActionSize();
|
||||
auto rightActionMargins = rightActionSize.isEmpty()
|
||||
? QMargins()
|
||||
: row->rightActionMargins();
|
||||
auto &name = row->name();
|
||||
auto namex = _st.item.namePosition.x();
|
||||
auto namew = width() - namex - skipRight;
|
||||
if (!actionSize.isEmpty()) {
|
||||
namew -= actionMargins.left()
|
||||
+ actionSize.width()
|
||||
+ actionMargins.right()
|
||||
auto namew = outerWidth - namex - skipRight;
|
||||
if (!rightActionSize.isEmpty()) {
|
||||
namew -= rightActionMargins.left()
|
||||
+ rightActionSize.width()
|
||||
+ rightActionMargins.right()
|
||||
- skipRight;
|
||||
}
|
||||
auto statusw = namew;
|
||||
@@ -1465,20 +1533,6 @@ crl::time PeerListContent::paintRow(
|
||||
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
|
||||
name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());
|
||||
|
||||
if (!actionSize.isEmpty()) {
|
||||
auto actionLeft = width()
|
||||
- actionMargins.right()
|
||||
- actionSize.width();
|
||||
auto actionTop = actionMargins.top();
|
||||
row->paintAction(
|
||||
p,
|
||||
actionLeft,
|
||||
actionTop,
|
||||
width(),
|
||||
selected,
|
||||
actionSelected);
|
||||
}
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
if (row->isSearchResult()
|
||||
&& !_mentionHighlight.isEmpty()
|
||||
@@ -1507,6 +1561,13 @@ crl::time PeerListContent::paintRow(
|
||||
} else {
|
||||
row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
|
||||
}
|
||||
|
||||
row->elementsPaint(
|
||||
p,
|
||||
width(),
|
||||
selected,
|
||||
(active.index == index) ? active.element : 0);
|
||||
|
||||
return refreshStatusIn;
|
||||
}
|
||||
|
||||
@@ -1572,7 +1633,7 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
}
|
||||
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.action = false;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0;
|
||||
auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height();
|
||||
@@ -1797,7 +1858,7 @@ void PeerListContent::setSelected(Selected selected) {
|
||||
}
|
||||
_selected = selected;
|
||||
updateRow(_selected.index);
|
||||
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
|
||||
setCursor(_selected.element ? style::cur_pointer : style::cur_default);
|
||||
|
||||
_selectedIndex = _selected.index.value;
|
||||
}
|
||||
@@ -1858,25 +1919,30 @@ void PeerListContent::selectByMouse(QPoint globalPosition) {
|
||||
rowsPointY - (selected.index.value * _rowHeight)))) {
|
||||
selected = Selected();
|
||||
} else if (!customMode) {
|
||||
if (getActiveActionRect(row, selected.index).contains(point)) {
|
||||
selected.action = true;
|
||||
for (auto i = 0, count = row->elementsCount(); i != count; ++i) {
|
||||
const auto rect = getElementRect(row, selected.index, i + 1);
|
||||
if (rect.contains(point)) {
|
||||
selected.element = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelected(selected);
|
||||
}
|
||||
|
||||
QRect PeerListContent::getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const {
|
||||
auto actionSize = row->actionSize();
|
||||
if (actionSize.isEmpty() || row->actionDisabled()) {
|
||||
QRect PeerListContent::getElementRect(
|
||||
not_null<PeerListRow*> row,
|
||||
RowIndex index,
|
||||
int element) const {
|
||||
if (row->elementDisabled(element)) {
|
||||
return QRect();
|
||||
}
|
||||
auto actionMargins = row->actionMargins();
|
||||
auto actionRight = actionMargins.right();
|
||||
auto actionTop = actionMargins.top();
|
||||
auto actionLeft = width() - actionRight - actionSize.width();
|
||||
auto rowTop = getRowTop(index);
|
||||
return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height());
|
||||
const auto geometry = row->elementGeometry(element, width());
|
||||
if (geometry.isEmpty()) {
|
||||
return QRect();
|
||||
}
|
||||
return geometry.translated(0, getRowTop(index));
|
||||
}
|
||||
|
||||
int PeerListContent::rowsTop() const {
|
||||
|
||||
@@ -47,6 +47,7 @@ using PaintRoundImageCallback = Fn<void(
|
||||
bool respectSavedMessagesChat);
|
||||
|
||||
using PeerListRowId = uint64;
|
||||
|
||||
class PeerListRow {
|
||||
public:
|
||||
enum class State {
|
||||
@@ -100,20 +101,17 @@ public:
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected);
|
||||
virtual QSize actionSize() const {
|
||||
|
||||
virtual QSize rightActionSize() const {
|
||||
return QSize();
|
||||
}
|
||||
virtual bool actionDisabled() const {
|
||||
return false;
|
||||
}
|
||||
virtual QMargins actionMargins() const {
|
||||
virtual QMargins rightActionMargins() const {
|
||||
return QMargins();
|
||||
}
|
||||
virtual void addActionRipple(QPoint point, Fn<void()> updateCallback) {
|
||||
virtual bool rightActionDisabled() const {
|
||||
return false;
|
||||
}
|
||||
virtual void stopLastActionRipple() {
|
||||
}
|
||||
virtual void paintAction(
|
||||
virtual void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
@@ -121,6 +119,28 @@ public:
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
}
|
||||
virtual void rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
}
|
||||
virtual void rightActionStopLastRipple() {
|
||||
}
|
||||
|
||||
// By default elements code falls back to a simple right action code.
|
||||
virtual int elementsCount() const;
|
||||
virtual QRect elementGeometry(int element, int outerWidth) const;
|
||||
virtual bool elementDisabled(int element) const;
|
||||
virtual bool elementOnlySelect(int element) const;
|
||||
virtual void elementAddRipple(
|
||||
int element,
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback);
|
||||
virtual void elementsStopLastRipple();
|
||||
virtual void elementsPaint(
|
||||
Painter &p,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
int selectedElement);
|
||||
|
||||
virtual void refreshName(const style::PeerListItem &st);
|
||||
const Ui::Text::String &name() const {
|
||||
@@ -405,11 +425,21 @@ public:
|
||||
const style::PeerList &computeListSt() const;
|
||||
const style::MultiSelect &computeSelectSt() const;
|
||||
|
||||
virtual void prepare() = 0;
|
||||
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
||||
virtual Main::Session &session() const = 0;
|
||||
virtual void rowActionClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
virtual void prepare() = 0;
|
||||
|
||||
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
||||
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
// By default elements code falls back to a simple right action code.
|
||||
virtual void rowElementClicked(not_null<PeerListRow*> row, int element) {
|
||||
if (element == 1) {
|
||||
rowRightActionClicked(row);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void loadMoreRows() {
|
||||
}
|
||||
virtual void itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
@@ -622,7 +652,7 @@ protected:
|
||||
int visibleBottom) override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void enterEventHook(QEvent *e) override;
|
||||
void enterEventHook(QEnterEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
@@ -653,15 +683,20 @@ private:
|
||||
struct Selected {
|
||||
Selected() {
|
||||
}
|
||||
Selected(RowIndex index, bool action) : index(index), action(action) {
|
||||
Selected(RowIndex index, int element)
|
||||
: index(index)
|
||||
, element(element) {
|
||||
}
|
||||
Selected(int index, bool action) : index(index), action(action) {
|
||||
Selected(int index, int element)
|
||||
: index(index)
|
||||
, element(element) {
|
||||
}
|
||||
|
||||
RowIndex index;
|
||||
bool action = false;
|
||||
int element = 0;
|
||||
};
|
||||
friend inline bool operator==(Selected a, Selected b) {
|
||||
return (a.index == b.index) && (a.action == b.action);
|
||||
return (a.index == b.index) && (a.element == b.element);
|
||||
}
|
||||
friend inline bool operator!=(Selected a, Selected b) {
|
||||
return !(a == b);
|
||||
@@ -689,8 +724,13 @@ private:
|
||||
void updateRow(RowIndex row);
|
||||
int getRowTop(RowIndex row) const;
|
||||
PeerListRow *getRow(RowIndex element);
|
||||
RowIndex findRowIndex(not_null<PeerListRow*> row, RowIndex hint = RowIndex());
|
||||
QRect getActiveActionRect(not_null<PeerListRow*> row, RowIndex index) const;
|
||||
RowIndex findRowIndex(
|
||||
not_null<PeerListRow*> row,
|
||||
RowIndex hint = RowIndex());
|
||||
QRect getElementRect(
|
||||
not_null<PeerListRow*> row,
|
||||
RowIndex index,
|
||||
int element) const;
|
||||
|
||||
bool showRowMenu(
|
||||
RowIndex index,
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -136,11 +136,11 @@ void PeerListRowWithLink::lazyInitialize(const style::PeerListItem &st) {
|
||||
refreshActionLink();
|
||||
}
|
||||
|
||||
QSize PeerListRowWithLink::actionSize() const {
|
||||
QSize PeerListRowWithLink::rightActionSize() const {
|
||||
return QSize(_actionWidth, st::normalFont->height);
|
||||
}
|
||||
|
||||
QMargins PeerListRowWithLink::actionMargins() const {
|
||||
QMargins PeerListRowWithLink::rightActionMargins() const {
|
||||
return QMargins(
|
||||
st::contactsCheckPosition.x(),
|
||||
(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2,
|
||||
@@ -148,7 +148,7 @@ QMargins PeerListRowWithLink::actionMargins() const {
|
||||
0);
|
||||
}
|
||||
|
||||
void PeerListRowWithLink::paintAction(
|
||||
void PeerListRowWithLink::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
@@ -482,7 +482,7 @@ void AddBotToGroupBoxController::shareBotGame(not_null<PeerData*> chat) {
|
||||
return tr::lng_bot_sure_share_game_group(tr::now, lt_group, chat->name);
|
||||
}();
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(confirmText, std::move(send)),
|
||||
Box<Ui::ConfirmBox>(confirmText, std::move(send)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
if (const auto megagroup = chat->asMegagroup()) {
|
||||
if (!megagroup->canAddMembers()) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_error_cant_add_member(tr::now)),
|
||||
Box<Ui::InformBox>(tr::lng_error_cant_add_member(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
@@ -500,7 +500,7 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
});
|
||||
auto confirmText = tr::lng_bot_sure_invite(tr::now, lt_group, chat->name);
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(confirmText, send),
|
||||
Box<Ui::ConfirmBox>(confirmText, send),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ public:
|
||||
|
||||
private:
|
||||
void refreshActionLink();
|
||||
QSize actionSize() const override;
|
||||
QMargins actionMargins() const override;
|
||||
void paintAction(
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
|
||||
@@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -105,7 +106,8 @@ void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
} else if (count >= serverConfig.chatSizeMax
|
||||
&& count < serverConfig.megagroupSizeMax) {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_profile_add_more_after_create(tr::now)),
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_profile_add_more_after_create(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
}
|
||||
@@ -565,20 +567,20 @@ void AddSpecialBoxController::showAdmin(
|
||||
if (canBanMembers) {
|
||||
if (!sure) {
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_sure_add_admin_unremove(tr::now),
|
||||
showAdminSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
tr::lng_error_cant_add_admin_unban(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
tr::lng_error_cant_add_admin_invite(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
@@ -588,14 +590,14 @@ void AddSpecialBoxController::showAdmin(
|
||||
if (canBanMembers) {
|
||||
if (!sure) {
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_sure_add_admin_unremove(tr::now),
|
||||
showAdminSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
tr::lng_error_cant_add_admin_unban(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
@@ -608,7 +610,7 @@ void AddSpecialBoxController::showAdmin(
|
||||
? tr::lng_sure_add_admin_invite
|
||||
: tr::lng_sure_add_admin_invite_channel)(tr::now);
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
showAdminSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -616,7 +618,8 @@ void AddSpecialBoxController::showAdmin(
|
||||
}
|
||||
} else {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_error_cant_add_admin_invite(tr::now)),
|
||||
Box<Ui::InformBox>(
|
||||
tr::lng_error_cant_add_admin_invite(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
@@ -716,7 +719,7 @@ void AddSpecialBoxController::showRestricted(
|
||||
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
|
||||
if (!sure) {
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_sure_ban_admin(tr::now),
|
||||
showRestrictedSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -724,7 +727,7 @@ void AddSpecialBoxController::showRestricted(
|
||||
}
|
||||
} else {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
|
||||
Box<Ui::InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
@@ -813,7 +816,7 @@ void AddSpecialBoxController::kickUser(
|
||||
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
|
||||
if (!sure) {
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_sure_ban_admin(tr::now),
|
||||
kickUserSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -821,7 +824,7 @@ void AddSpecialBoxController::kickUser(
|
||||
}
|
||||
} else {
|
||||
Ui::show(
|
||||
Box<InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
|
||||
Box<Ui::InformBox>(tr::lng_error_cant_ban_admin(tr::now)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
@@ -836,7 +839,7 @@ void AddSpecialBoxController::kickUser(
|
||||
lt_user,
|
||||
participant->name);
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(text, kickUserSure),
|
||||
Box<Ui::ConfirmBox>(text, kickUserSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
@@ -26,8 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxUserFirstLastName = 64; // See also add_contact_box.
|
||||
|
||||
QString UserPhone(not_null<UserData*> user) {
|
||||
const auto phone = user->phone();
|
||||
return phone.isEmpty()
|
||||
@@ -222,8 +221,8 @@ void Controller::initNameFields(
|
||||
};
|
||||
QObject::connect(first, &Ui::InputField::submitted, submit);
|
||||
QObject::connect(last, &Ui::InputField::submitted, submit);
|
||||
first->setMaxLength(kMaxUserFirstLastName);
|
||||
first->setMaxLength(kMaxUserFirstLastName);
|
||||
first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
|
||||
first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName);
|
||||
}
|
||||
|
||||
void Controller::setupWarning() {
|
||||
|
||||
@@ -10,12 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "facades.h"
|
||||
@@ -63,12 +64,12 @@ Controller::Controller(
|
||||
, _chat(chat)
|
||||
, _chats(std::move(chats))
|
||||
, _callback(std::move(callback)) {
|
||||
base::ObservableViewer(
|
||||
channel->session().api().fullPeerUpdated()
|
||||
) | rpl::start_with_next([=](PeerData *peer) {
|
||||
if (peer == _waitForFull) {
|
||||
choose(std::exchange(_waitForFull, nullptr));
|
||||
}
|
||||
channel->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.peer == _waitForFull);
|
||||
}) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
choose(std::exchange(_waitForFull, nullptr));
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ void Controller::choose(not_null<ChannelData*> chat) {
|
||||
onstack(chat);
|
||||
};
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
tr::lng_manage_discussion_group_link(tr::now),
|
||||
sure),
|
||||
@@ -184,7 +185,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||
chat->session().api().migrateChat(chat, crl::guard(this, done));
|
||||
};
|
||||
Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
tr::lng_manage_discussion_group_link(tr::now),
|
||||
sure),
|
||||
|
||||
@@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
@@ -34,7 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -453,7 +452,14 @@ void EditAdminBox::transferOwnership() {
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
_checkTransferRequestId = 0;
|
||||
if (!handleTransferPasswordError(error)) {
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
|
||||
const auto callback = crl::guard(this, [=] {
|
||||
transferOwnershipChecked();
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
});
|
||||
*box = getDelegate()->show(Box<Ui::ConfirmBox>(
|
||||
tr::lng_rights_transfer_about(
|
||||
tr::now,
|
||||
lt_group,
|
||||
@@ -462,7 +468,7 @@ void EditAdminBox::transferOwnership() {
|
||||
Ui::Text::Bold(user()->shortName()),
|
||||
Ui::Text::RichLangValue),
|
||||
tr::lng_rights_transfer_sure(tr::now),
|
||||
crl::guard(this, [=] { transferOwnershipChecked(); })));
|
||||
callback));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
@@ -570,7 +576,7 @@ void EditAdminBox::sendTransferRequestFrom(
|
||||
|| (type == qstr("SESSION_TOO_FRESH_XXX"));
|
||||
}();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
getDelegate()->show(Box<InformBox>(problem));
|
||||
getDelegate()->show(Box<Ui::InformBox>(problem));
|
||||
if (box) {
|
||||
box->closeBox();
|
||||
}
|
||||
@@ -703,7 +709,7 @@ void EditRestrictedBox::showRestrictUntil() {
|
||||
highlighted,
|
||||
[this](const QDate &date) {
|
||||
setRestrictUntil(
|
||||
static_cast<int>(base::QDateToDateTime(date).toTime_t()));
|
||||
static_cast<int>(date.startOfDay().toSecsSinceEpoch()));
|
||||
}),
|
||||
Ui::LayerOption::KeepOther);
|
||||
_restrictUntilBox->setMaxDate(
|
||||
|
||||
@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/add_participants_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
@@ -924,9 +926,8 @@ void ParticipantsBoxController::Start(
|
||||
});
|
||||
}
|
||||
};
|
||||
Ui::show(
|
||||
Box<PeerListBox>(std::move(controller), initBox),
|
||||
Ui::LayerOption::KeepOther);
|
||||
navigation->parentController()->show(
|
||||
Box<PeerListBox>(std::move(controller), initBox));
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::addNewItem() {
|
||||
@@ -1434,11 +1435,17 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
showRestricted(user);
|
||||
} else {
|
||||
Assert(_navigation != nullptr);
|
||||
_navigation->showPeerInfo(participant);
|
||||
if (_role != Role::Profile) {
|
||||
_navigation->parentController()->show(PrepareShortInfoBox(
|
||||
participant,
|
||||
_navigation));
|
||||
} else {
|
||||
_navigation->showPeerInfo(participant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::rowActionClicked(
|
||||
void ParticipantsBoxController::rowRightActionClicked(
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
@@ -1690,7 +1697,7 @@ void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant)
|
||||
lt_user,
|
||||
user ? user->firstName : participant->name);
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
tr::lng_box_remove(tr::now),
|
||||
crl::guard(this, [=] { kickParticipantSure(participant); })),
|
||||
@@ -1729,7 +1736,7 @@ void ParticipantsBoxController::kickParticipantSure(
|
||||
|
||||
void ParticipantsBoxController::removeAdmin(not_null<UserData*> user) {
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_profile_sure_remove_admin(
|
||||
tr::now,
|
||||
lt_user,
|
||||
|
||||
@@ -157,7 +157,7 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowActionClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
|
||||
18
Telegram/SourceFiles/boxes/peers/edit_peer_common.h
Normal file
18
Telegram/SourceFiles/boxes/peers/edit_peer_common.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui::EditPeer {
|
||||
|
||||
constexpr auto kMaxGroupChannelTitle = 128;
|
||||
constexpr auto kMaxUserFirstLastName = 64;
|
||||
constexpr auto kMaxChannelDescription = 255;
|
||||
constexpr auto kMinUsernameLength = 5;
|
||||
constexpr auto kUsernameCheckTimeout = crl::time(200);
|
||||
|
||||
} // namespace Ui::EditPeer
|
||||
@@ -8,16 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "boxes/peers/edit_peer_history_visibility_box.h"
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
@@ -249,9 +252,6 @@ void ShowEditPermissions(
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxGroupChannelTitle = 128; // See also add_contact_box.
|
||||
constexpr auto kMaxChannelDescription = 255; // See also add_contact_box.
|
||||
|
||||
class Controller : public base::has_weak_ptr {
|
||||
public:
|
||||
Controller(
|
||||
@@ -298,6 +298,7 @@ private:
|
||||
void fillSignaturesButton();
|
||||
void fillHistoryVisibilityButton();
|
||||
void fillManageSection();
|
||||
void fillPendingRequestsButton();
|
||||
|
||||
void submitTitle();
|
||||
void submitDescription();
|
||||
@@ -475,7 +476,7 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
|
||||
: tr::lng_dlg_new_channel_name)(),
|
||||
_peer->name),
|
||||
st::editPeerTitleMargins);
|
||||
result->entity()->setMaxLength(kMaxGroupChannelTitle);
|
||||
result->entity()->setMaxLength(Ui::EditPeer::kMaxGroupChannelTitle);
|
||||
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
result->entity()->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
@@ -509,7 +510,7 @@ object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
|
||||
tr::lng_create_group_description(),
|
||||
_peer->about()),
|
||||
st::editPeerDescriptionMargins);
|
||||
result->entity()->setMaxLength(kMaxChannelDescription);
|
||||
result->entity()->setMaxLength(Ui::EditPeer::kMaxChannelDescription);
|
||||
result->entity()->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
result->entity()->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
@@ -846,6 +847,8 @@ void Controller::fillHistoryVisibilityButton() {
|
||||
void Controller::fillManageSection() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto isChannel = (!chat);
|
||||
@@ -1043,6 +1046,9 @@ void Controller::fillManageSection() {
|
||||
},
|
||||
st::infoIconMembers);
|
||||
}
|
||||
|
||||
fillPendingRequestsButton();
|
||||
|
||||
if (canViewKicked) {
|
||||
AddButtonWithCount(
|
||||
_controls.buttonsLayout,
|
||||
@@ -1090,6 +1096,33 @@ void Controller::fillManageSection() {
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::fillPendingRequestsButton() {
|
||||
auto pendingRequestsCount = Info::Profile::MigratedOrMeValue(
|
||||
_peer
|
||||
) | rpl::map(
|
||||
Info::Profile::PendingRequestsCountValue
|
||||
) | rpl::flatten_latest(
|
||||
) | rpl::start_spawning(_controls.buttonsLayout->lifetime());
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
_controls.buttonsLayout,
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
_controls.buttonsLayout)));
|
||||
AddButtonWithCount(
|
||||
wrap->entity(),
|
||||
(_isGroup
|
||||
? tr::lng_manage_peer_requests()
|
||||
: tr::lng_manage_peer_requests_channel()),
|
||||
rpl::duplicate(pendingRequestsCount) | ToPositiveNumberString(),
|
||||
[=] { RequestsBoxController::Start(_navigation, _peer); },
|
||||
st::infoIconRequests);
|
||||
std::move(
|
||||
pendingRequestsCount
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
wrap->toggle(count > 0, anim::type::instant);
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
|
||||
void Controller::submitTitle() {
|
||||
Expects(_controls.title != nullptr);
|
||||
|
||||
@@ -1471,7 +1504,7 @@ void Controller::savePhoto() {
|
||||
? _controls.photo->takeResultImage()
|
||||
: QImage();
|
||||
if (!image.isNull()) {
|
||||
_peer->session().api().uploadPeerPhoto(_peer, std::move(image));
|
||||
_peer->session().api().peerPhoto().upload(_peer, std::move(image));
|
||||
}
|
||||
_box->closeBox();
|
||||
}
|
||||
@@ -1487,7 +1520,7 @@ void Controller::deleteWithConfirmation() {
|
||||
deleteChannel();
|
||||
});
|
||||
_navigation->parentController()->show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
text,
|
||||
tr::lng_box_delete(tr::now),
|
||||
st::attentionBoxButton,
|
||||
@@ -1514,7 +1547,7 @@ void Controller::deleteChannel() {
|
||||
session->api().applyUpdates(result);
|
||||
//}).fail([=](const MTP::Error &error) {
|
||||
// if (error.type() == qstr("CHANNEL_TOO_LARGE")) {
|
||||
// Ui::show(Box<InformBox>(tr::lng_cant_delete_channel(tr::now)));
|
||||
// Ui::show(Box<Ui::InformBox>(tr::lng_cant_delete_channel(tr::now)));
|
||||
// }
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
@@ -24,13 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/boxes/edit_invite_link.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
|
||||
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
|
||||
#include "history/history_message.h" // GetErrorTextForSending.
|
||||
#include "history/history.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "mainwindow.h"
|
||||
#include "facades.h" // Ui::showPerProfile.
|
||||
@@ -57,30 +59,110 @@ constexpr auto kShareQrPadding = 16;
|
||||
|
||||
using LinkData = Api::InviteLink;
|
||||
|
||||
class Controller final : public PeerListController {
|
||||
class RequestedRow final : public PeerListRow {
|
||||
public:
|
||||
RequestedRow(not_null<PeerData*> peer, TimeId date);
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
};
|
||||
|
||||
RequestedRow::RequestedRow(not_null<PeerData*> peer, TimeId date)
|
||||
: PeerListRow(peer) {
|
||||
setCustomStatus(PrepareRequestedRowStatus(date));
|
||||
}
|
||||
|
||||
QSize RequestedRow::rightActionSize() const {
|
||||
return QSize(
|
||||
st::inviteLinkThreeDotsIcon.width(),
|
||||
st::inviteLinkThreeDotsIcon.height());
|
||||
}
|
||||
|
||||
QMargins RequestedRow::rightActionMargins() const {
|
||||
return QMargins(
|
||||
0,
|
||||
(st::peerListBoxItem.height - rightActionSize().height()) / 2,
|
||||
st::inviteLinkThreeDotsSkip,
|
||||
0);
|
||||
}
|
||||
|
||||
void RequestedRow::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
(actionSelected
|
||||
? st::inviteLinkThreeDotsIconOver
|
||||
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
|
||||
}
|
||||
|
||||
class Controller final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
enum class Role {
|
||||
Requested,
|
||||
Joined,
|
||||
};
|
||||
Controller(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
rpl::producer<LinkData> data);
|
||||
rpl::producer<LinkData> data,
|
||||
Role role);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
rpl::producer<int> boxHeightValue() const override;
|
||||
int descriptionTopSkipMin() const override;
|
||||
|
||||
struct Processed {
|
||||
not_null<UserData*> user;
|
||||
bool approved = false;
|
||||
};
|
||||
[[nodiscard]] rpl::producer<Processed> processed() const {
|
||||
return _processed.events();
|
||||
}
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
|
||||
void setupAboveJoinedWidget();
|
||||
void appendSlice(const Api::JoinedByLinkSlice &slice);
|
||||
void addHeaderBlock(not_null<Ui::VerticalLayout*> container);
|
||||
not_null<Ui::SlideWrap<>*> addRequestedListBlock(
|
||||
not_null<Ui::VerticalLayout*> container);
|
||||
void updateWithProcessed(Processed processed);
|
||||
|
||||
[[nodiscard]] rpl::producer<LinkData> dataValue() const;
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row);
|
||||
void processRequest(not_null<UserData*> user, bool approved);
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
const Role _role = Role::Joined;
|
||||
rpl::variable<LinkData> _data;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
rpl::event_stream<Processed> _processed;
|
||||
|
||||
QString _link;
|
||||
bool _revoked = false;
|
||||
|
||||
@@ -220,10 +302,12 @@ void QrBox(
|
||||
Controller::Controller(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
rpl::producer<LinkData> data)
|
||||
rpl::producer<LinkData> data,
|
||||
Role role)
|
||||
: _peer(peer)
|
||||
, _role(role)
|
||||
, _data(LinkData{ .admin = admin })
|
||||
, _api(&_peer->session().api().instance()) {
|
||||
, _api(&session().api().instance()) {
|
||||
_data = std::move(data);
|
||||
const auto current = _data.current();
|
||||
_link = current.link;
|
||||
@@ -386,7 +470,87 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<Ui::SlideWrap<>*> Controller::addRequestedListBlock(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
using namespace Settings;
|
||||
|
||||
auto result = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
container)));
|
||||
const auto wrap = result->entity();
|
||||
// Make this container occupy full width.
|
||||
wrap->add(object_ptr<Ui::RpWidget>(wrap));
|
||||
AddDivider(wrap);
|
||||
AddSkip(wrap);
|
||||
auto requestedCount = dataValue(
|
||||
) | rpl::filter([](const LinkData &data) {
|
||||
return data.requested > 0;
|
||||
}) | rpl::map([=](const LinkData &data) {
|
||||
return float64(data.requested);
|
||||
});
|
||||
AddSubsectionTitle(
|
||||
wrap,
|
||||
tr::lng_group_invite_requested_full(
|
||||
lt_count_decimal,
|
||||
std::move(requestedCount)));
|
||||
|
||||
const auto delegate = container->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
Controller
|
||||
>(_peer, _data.current().admin, _data.value(), Role::Requested);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
controller->processed(
|
||||
) | rpl::start_with_next([=](Processed processed) {
|
||||
updateWithProcessed(processed);
|
||||
}, lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Controller::prepare() {
|
||||
if (_role == Role::Joined) {
|
||||
setupAboveJoinedWidget();
|
||||
|
||||
_allLoaded = (_data.current().usage == 0);
|
||||
|
||||
const auto &inviteLinks = session().api().inviteLinks();
|
||||
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
|
||||
if (slice) {
|
||||
appendSlice(*slice);
|
||||
}
|
||||
} else {
|
||||
_allLoaded = (_data.current().requested == 0);
|
||||
}
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void Controller::updateWithProcessed(Processed processed) {
|
||||
const auto user = processed.user;
|
||||
auto updated = _data.current();
|
||||
if (processed.approved) {
|
||||
++updated.usage;
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListPrependRow(
|
||||
std::make_unique<PeerListRow>(user));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
if (updated.requested > 0) {
|
||||
--updated.requested;
|
||||
}
|
||||
session().api().inviteLinks().applyExternalUpdate(_peer, updated);
|
||||
}
|
||||
|
||||
void Controller::setupAboveJoinedWidget() {
|
||||
using namespace Settings;
|
||||
|
||||
auto header = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
@@ -406,6 +570,8 @@ void Controller::prepare() {
|
||||
rpl::single(langDateTime(base::unixtime::parse(current.date))));
|
||||
AddSkip(container, st::membersMarginBottom);
|
||||
|
||||
auto requestedWrap = addRequestedListBlock(container);
|
||||
|
||||
const auto listHeaderWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
@@ -484,6 +650,8 @@ void Controller::prepare() {
|
||||
} else {
|
||||
remaining->show();
|
||||
}
|
||||
|
||||
requestedWrap->toggle(data.requested > 0, anim::type::instant);
|
||||
}, remaining->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
@@ -500,23 +668,19 @@ void Controller::prepare() {
|
||||
_headerWidget = header.data();
|
||||
|
||||
delegate()->peerListSetAboveWidget(std::move(header));
|
||||
_allLoaded = (current.usage == 0);
|
||||
|
||||
const auto &inviteLinks = _peer->session().api().inviteLinks();
|
||||
const auto slice = inviteLinks.joinedFirstSliceLoaded(_peer, _link);
|
||||
if (slice) {
|
||||
appendSlice(*slice);
|
||||
}
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void Controller::loadMoreRows() {
|
||||
if (_requestId || _allLoaded) {
|
||||
return;
|
||||
}
|
||||
using Flag = MTPmessages_GetChatInviteImporters::Flag;
|
||||
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(Flag::f_link
|
||||
| (_role == Role::Requested ? Flag::f_requested : Flag(0))),
|
||||
_peer->input,
|
||||
MTP_string(_link),
|
||||
MTPstring(), // q
|
||||
MTP_int(_lastUser ? _lastUser->date : 0),
|
||||
_lastUser ? _lastUser->user->inputUser : MTP_inputUserEmpty(),
|
||||
MTP_int(_lastUser ? kPerPage : kFirstPage)
|
||||
@@ -534,8 +698,9 @@ void Controller::loadMoreRows() {
|
||||
void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
|
||||
for (const auto &user : slice.users) {
|
||||
_lastUser = user;
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<PeerListRow>(user.user));
|
||||
delegate()->peerListAppendRow((_role == Role::Requested)
|
||||
? std::make_unique<RequestedRow>(user.user, user.date)
|
||||
: std::make_unique<PeerListRow>(user.user));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
if (delegate()->peerListFullRowsCount() > 0) {
|
||||
@@ -550,6 +715,82 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
Ui::showPeerProfile(row->peer());
|
||||
}
|
||||
|
||||
void Controller::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
if (_role != Role::Requested) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> Controller::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
auto result = createRowContextMenu(parent, row);
|
||||
|
||||
if (result) {
|
||||
// First clear _menu value, so that we don't check row positions yet.
|
||||
base::take(_menu);
|
||||
|
||||
// Here unique_qptr is used like a shared pointer, where
|
||||
// not the last destroyed pointer destroys the object, but the first.
|
||||
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> Controller::createRowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto user = row->peer()->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
const auto add = _peer->isBroadcast()
|
||||
? tr::lng_group_requests_add_channel(tr::now)
|
||||
: tr::lng_group_requests_add(tr::now);
|
||||
result->addAction(add, [=] {
|
||||
processRequest(user, true);
|
||||
});
|
||||
result->addAction(tr::lng_group_requests_dismiss(tr::now), [=] {
|
||||
processRequest(user, false);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void Controller::processRequest(
|
||||
not_null<UserData*> user,
|
||||
bool approved) {
|
||||
const auto done = crl::guard(this, [=] {
|
||||
_processed.fire({ user, approved });
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
if (approved) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = (_peer->isBroadcast()
|
||||
? tr::lng_group_requests_was_added_channel
|
||||
: tr::lng_group_requests_was_added)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->name),
|
||||
Ui::Text::WithEntities)
|
||||
});
|
||||
}
|
||||
});
|
||||
const auto fail = crl::guard(this, [=] {
|
||||
_processed.fire({ user, false });
|
||||
});
|
||||
session().api().inviteLinks().processRequest(
|
||||
_peer,
|
||||
_data.current().link,
|
||||
user,
|
||||
approved,
|
||||
done,
|
||||
fail);
|
||||
}
|
||||
|
||||
Main::Session &Controller::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
@@ -644,6 +885,9 @@ void AddPermanentLinkBlock(
|
||||
const auto value = container->lifetime().make_state<
|
||||
rpl::variable<LinkData>
|
||||
>();
|
||||
const auto currentLinkFields = container->lifetime().make_state<
|
||||
Api::InviteLink
|
||||
>(Api::InviteLink{ .admin = admin });
|
||||
if (admin->isSelf()) {
|
||||
*value = peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
@@ -652,11 +896,19 @@ void AddPermanentLinkBlock(
|
||||
const auto &links = peer->session().api().inviteLinks().myLinks(
|
||||
peer).links;
|
||||
const auto link = links.empty() ? nullptr : &links.front();
|
||||
return (link && link->permanent && !link->revoked)
|
||||
? LinkData{ link->link, link->usage }
|
||||
: LinkData();
|
||||
if (link && link->permanent && !link->revoked) {
|
||||
*currentLinkFields = *link;
|
||||
return LinkData{ link->link, link->usage };
|
||||
}
|
||||
return LinkData();
|
||||
});
|
||||
} else {
|
||||
rpl::duplicate(
|
||||
fromList
|
||||
) | rpl::start_with_next([=](const Api::InviteLink &link) {
|
||||
*currentLinkFields = link;
|
||||
}, container->lifetime());
|
||||
|
||||
*value = std::move(
|
||||
fromList
|
||||
) | rpl::map([](const Api::InviteLink &link) {
|
||||
@@ -680,7 +932,7 @@ void AddPermanentLinkBlock(
|
||||
}
|
||||
});
|
||||
const auto revokeLink = crl::guard(weak, [=] {
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
|
||||
const auto done = crl::guard(weak, [=] {
|
||||
const auto close = [=] {
|
||||
if (*box) {
|
||||
@@ -694,7 +946,9 @@ void AddPermanentLinkBlock(
|
||||
close);
|
||||
});
|
||||
*box = Ui::show(
|
||||
Box<ConfirmBox>(tr::lng_group_invite_about_new(tr::now), done),
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_group_invite_about_new(tr::now),
|
||||
done),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
|
||||
@@ -813,6 +1067,9 @@ void AddPermanentLinkBlock(
|
||||
state->content.value(),
|
||||
st::inviteLinkJoinedRowPadding
|
||||
)->setClickedCallback([=] {
|
||||
if (!currentLinkFields->link.isEmpty()) {
|
||||
ShowInviteLinkBox(peer, *currentLinkFields);
|
||||
}
|
||||
});
|
||||
|
||||
container->add(object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>(
|
||||
@@ -868,7 +1125,7 @@ void ShareInviteLinkBox(not_null<PeerData*> peer, const QString &link) {
|
||||
}
|
||||
text.append(error.first);
|
||||
Ui::show(
|
||||
Box<InformBox>(text),
|
||||
Box<Ui::InformBox>(text),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
@@ -938,27 +1195,37 @@ void EditLink(
|
||||
peer->session().api().inviteLinks().create(
|
||||
peer,
|
||||
finish,
|
||||
result.label,
|
||||
result.expireDate,
|
||||
result.usageLimit);
|
||||
result.usageLimit,
|
||||
result.requestApproval);
|
||||
} else {
|
||||
peer->session().api().inviteLinks().edit(
|
||||
peer,
|
||||
data.admin,
|
||||
result.link,
|
||||
result.label,
|
||||
result.expireDate,
|
||||
result.usageLimit,
|
||||
result.requestApproval,
|
||||
finish);
|
||||
}
|
||||
};
|
||||
const auto isGroup = !peer->isBroadcast();
|
||||
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
|
||||
*box = Ui::show(
|
||||
(creating
|
||||
? Box(Ui::CreateInviteLinkBox, done)
|
||||
? Box(Ui::CreateInviteLinkBox, isGroup, isPublic, done)
|
||||
: Box(
|
||||
Ui::EditInviteLinkBox,
|
||||
Fields{
|
||||
.link = data.link,
|
||||
.label = data.label,
|
||||
.expireDate = data.expireDate,
|
||||
.usageLimit = data.usageLimit
|
||||
.usageLimit = data.usageLimit,
|
||||
.requestApproval = data.requestApproval,
|
||||
.isGroup = isGroup,
|
||||
.isPublic = isPublic,
|
||||
},
|
||||
done)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -968,7 +1235,7 @@ void RevokeLink(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link) {
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
|
||||
const auto revoke = [=] {
|
||||
const auto done = [=](const LinkData &data) {
|
||||
if (*box) {
|
||||
@@ -978,7 +1245,7 @@ void RevokeLink(
|
||||
peer->session().api().inviteLinks().revoke(peer, admin, link, done);
|
||||
};
|
||||
*box = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_group_invite_revoke_about(tr::now),
|
||||
revoke),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -988,7 +1255,7 @@ void DeleteLink(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link) {
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
|
||||
const auto sure = [=] {
|
||||
const auto finish = [=] {
|
||||
if (*box) {
|
||||
@@ -1002,7 +1269,7 @@ void DeleteLink(
|
||||
finish);
|
||||
};
|
||||
*box = Ui::show(
|
||||
Box<ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
|
||||
Box<Ui::ConfirmBox>(tr::lng_group_invite_delete_sure(tr::now), sure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -1033,7 +1300,9 @@ void ShowInviteLinkBox(
|
||||
return;
|
||||
}
|
||||
const auto now = base::unixtime::now();
|
||||
box->setTitle(link.revoked
|
||||
box->setTitle(!link.label.isEmpty()
|
||||
? rpl::single(link.label)
|
||||
: link.revoked
|
||||
? tr::lng_manage_peer_link_invite()
|
||||
: IsExpiredLink(link, now)
|
||||
? tr::lng_manage_peer_link_expired()
|
||||
@@ -1046,7 +1315,33 @@ void ShowInviteLinkBox(
|
||||
};
|
||||
Ui::show(
|
||||
Box<PeerListBox>(
|
||||
std::make_unique<Controller>(peer, link.admin, std::move(data)),
|
||||
std::make_unique<Controller>(
|
||||
peer,
|
||||
link.admin,
|
||||
std::move(data),
|
||||
Controller::Role::Joined),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
QString PrepareRequestedRowStatus(TimeId date) {
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
const auto parsed = base::unixtime::parse(date);
|
||||
const auto parsedDate = parsed.date();
|
||||
const auto time = parsed.time().toString(cTimeFormat());
|
||||
const auto generic = [&] {
|
||||
return tr::lng_group_requests_status_date_time(
|
||||
tr::now,
|
||||
lt_date,
|
||||
langDayOfMonth(parsedDate),
|
||||
lt_time,
|
||||
time);
|
||||
};
|
||||
return (parsedDate.addDays(1) < now.date())
|
||||
? generic()
|
||||
: (parsedDate.addDays(1) == now.date())
|
||||
? tr::lng_group_requests_status_yesterday(tr::now, lt_time, time)
|
||||
: (now.date() == parsedDate)
|
||||
? tr::lng_group_requests_status_today(tr::now, lt_time, time)
|
||||
: generic();
|
||||
}
|
||||
|
||||
@@ -50,3 +50,5 @@ void DeleteLink(
|
||||
void ShowInviteLinkBox(
|
||||
not_null<PeerData*> peer,
|
||||
const Api::InviteLink &link);
|
||||
|
||||
[[nodiscard]] QString PrepareRequestedRowStatus(TimeId date);
|
||||
|
||||
@@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_peer_invite_link.h"
|
||||
#include "settings/settings_common.h" // AddDivider.
|
||||
@@ -91,9 +91,9 @@ public:
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback() override;
|
||||
|
||||
QSize actionSize() const override;
|
||||
QMargins actionMargins() const override;
|
||||
void paintAction(
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
@@ -178,6 +178,16 @@ private:
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.usageLimit - link.usage);
|
||||
} else if (link.usage > 0 && link.requested > 0) {
|
||||
result += ", " + tr::lng_group_invite_requested(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.requested);
|
||||
} else if (link.requested > 0) {
|
||||
result = tr::lng_group_invite_requested_full(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
link.requested);
|
||||
}
|
||||
if (link.expireDate > now) {
|
||||
const auto left = (link.expireDate - now);
|
||||
@@ -197,7 +207,7 @@ private:
|
||||
void DeleteAllRevoked(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin) {
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto box = std::make_shared<QPointer<Ui::ConfirmBox>>();
|
||||
const auto sure = [=] {
|
||||
const auto finish = [=] {
|
||||
if (*box) {
|
||||
@@ -210,7 +220,9 @@ void DeleteAllRevoked(
|
||||
finish);
|
||||
};
|
||||
*box = Ui::show(
|
||||
Box<ConfirmBox>(tr::lng_group_invite_delete_all_sure(tr::now), sure),
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_group_invite_delete_all_sure(tr::now),
|
||||
sure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
@@ -239,8 +251,10 @@ not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgActive);
|
||||
const auto rect = icon->rect();
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(rect);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(rect);
|
||||
}
|
||||
st::inviteLinkCreateIcon.paintInCenter(p, rect);
|
||||
}, icon->lifetime());
|
||||
return result;
|
||||
@@ -263,12 +277,14 @@ void Row::update(const InviteLinkData &data, TimeId now) {
|
||||
_progressTillExpire = ComputeProgress(data, now);
|
||||
_color = ComputeColor(data, _progressTillExpire);
|
||||
setCustomStatus(ComputeStatus(data, now));
|
||||
refreshName(st::inviteLinkList.item);
|
||||
_delegate->rowUpdateRow(this);
|
||||
}
|
||||
|
||||
void Row::updateExpireProgress(TimeId now) {
|
||||
const auto updated = ComputeProgress(_data, now);
|
||||
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
|
||||
if (base::SafeRound(_progressTillExpire * 360)
|
||||
!= base::SafeRound(updated * 360)) {
|
||||
_progressTillExpire = updated;
|
||||
const auto color = ComputeColor(_data, _progressTillExpire);
|
||||
if (_color != color) {
|
||||
@@ -291,10 +307,14 @@ crl::time Row::updateExpireIn() const {
|
||||
if (_data.expireDate <= start) {
|
||||
return 0;
|
||||
}
|
||||
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
return base::SafeRound(
|
||||
(_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
if (!_data.label.isEmpty()) {
|
||||
return _data.label;
|
||||
}
|
||||
auto result = _data.link;
|
||||
return result.replace(
|
||||
qstr("https://"),
|
||||
@@ -323,21 +343,21 @@ PaintRoundImageCallback Row::generatePaintUserpicCallback() {
|
||||
};
|
||||
}
|
||||
|
||||
QSize Row::actionSize() const {
|
||||
QSize Row::rightActionSize() const {
|
||||
return QSize(
|
||||
st::inviteLinkThreeDotsIcon.width(),
|
||||
st::inviteLinkThreeDotsIcon.height());
|
||||
}
|
||||
|
||||
QMargins Row::actionMargins() const {
|
||||
QMargins Row::rightActionMargins() const {
|
||||
return QMargins(
|
||||
0,
|
||||
(st::inviteLinkList.item.height - actionSize().height()) / 2,
|
||||
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
|
||||
st::inviteLinkThreeDotsSkip,
|
||||
0);
|
||||
}
|
||||
|
||||
void Row::paintAction(
|
||||
void Row::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
@@ -367,7 +387,7 @@ public:
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowActionClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
@@ -531,7 +551,7 @@ void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data());
|
||||
}
|
||||
|
||||
void LinksController::rowActionClicked(not_null<PeerListRow*> row) {
|
||||
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
}
|
||||
|
||||
@@ -683,12 +703,14 @@ void LinksController::rowPaintIcon(
|
||||
auto p = QPainter(&icon);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(*bg);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto rect = QRect(0, 0, inner, inner);
|
||||
if (color == Color::Expiring || color == Color::ExpireSoon) {
|
||||
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto rect = QRect(0, 0, inner, inner);
|
||||
if (color == Color::Expiring || color == Color::ExpireSoon) {
|
||||
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
|
||||
}
|
||||
p.drawEllipse(rect);
|
||||
}
|
||||
p.drawEllipse(rect);
|
||||
(color == Color::Revoked
|
||||
? st::inviteLinkRevokedIcon
|
||||
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
|
||||
|
||||
680
Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp
Normal file
680
Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp
Normal file
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
|
||||
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kFirstPageCount = 16;
|
||||
constexpr auto kPerPage = 200;
|
||||
constexpr auto kServerSearchDelay = crl::time(1000);
|
||||
constexpr auto kAcceptButton = 1;
|
||||
constexpr auto kRejectButton = 2;
|
||||
|
||||
class RowDelegate {
|
||||
public:
|
||||
[[nodiscard]] virtual QSize rowAcceptButtonSize() = 0;
|
||||
[[nodiscard]] virtual QSize rowRejectButtonSize() = 0;
|
||||
virtual void rowPaintAccept(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) = 0;
|
||||
virtual void rowPaintReject(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) = 0;
|
||||
};
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(
|
||||
not_null<RowDelegate*> delegate,
|
||||
not_null<UserData*> user,
|
||||
TimeId date);
|
||||
|
||||
int elementsCount() const override;
|
||||
QRect elementGeometry(int element, int outerWidth) const override;
|
||||
bool elementDisabled(int element) const override;
|
||||
bool elementOnlySelect(int element) const override;
|
||||
void elementAddRipple(
|
||||
int element,
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) override;
|
||||
void elementsStopLastRipple() override;
|
||||
void elementsPaint(
|
||||
Painter &p,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
int selectedElement) override;
|
||||
|
||||
private:
|
||||
const not_null<RowDelegate*> _delegate;
|
||||
std::unique_ptr<Ui::RippleAnimation> _acceptRipple;
|
||||
std::unique_ptr<Ui::RippleAnimation> _rejectRipple;
|
||||
|
||||
};
|
||||
|
||||
Row::Row(
|
||||
not_null<RowDelegate*> delegate,
|
||||
not_null<UserData*> user,
|
||||
TimeId date)
|
||||
: PeerListRow(user)
|
||||
, _delegate(delegate) {
|
||||
setCustomStatus(PrepareRequestedRowStatus(date));
|
||||
}
|
||||
|
||||
int Row::elementsCount() const {
|
||||
return 2;
|
||||
}
|
||||
|
||||
QRect Row::elementGeometry(int element, int outerWidth) const {
|
||||
switch (element) {
|
||||
case kAcceptButton: {
|
||||
const auto size = _delegate->rowAcceptButtonSize();
|
||||
return QRect(st::requestAcceptPosition, size);
|
||||
} break;
|
||||
case kRejectButton: {
|
||||
const auto accept = _delegate->rowAcceptButtonSize();
|
||||
const auto size = _delegate->rowRejectButtonSize();
|
||||
return QRect(
|
||||
(st::requestAcceptPosition
|
||||
+ QPoint(accept.width() + st::requestButtonsSkip, 0)),
|
||||
size);
|
||||
} break;
|
||||
}
|
||||
return QRect();
|
||||
}
|
||||
|
||||
bool Row::elementDisabled(int element) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Row::elementOnlySelect(int element) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Row::elementAddRipple(
|
||||
int element,
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
const auto pointer = (element == kAcceptButton)
|
||||
? &_acceptRipple
|
||||
: (element == kRejectButton)
|
||||
? &_rejectRipple
|
||||
: nullptr;
|
||||
if (!pointer) {
|
||||
return;
|
||||
}
|
||||
auto &ripple = *pointer;
|
||||
if (!ripple) {
|
||||
auto mask = Ui::RippleAnimation::roundRectMask(
|
||||
(element == kAcceptButton
|
||||
? _delegate->rowAcceptButtonSize()
|
||||
: _delegate->rowRejectButtonSize()),
|
||||
st::buttonRadius);
|
||||
ripple = std::make_unique<Ui::RippleAnimation>(
|
||||
(element == kAcceptButton
|
||||
? st::requestsAcceptButton.ripple
|
||||
: st::requestsRejectButton.ripple),
|
||||
std::move(mask),
|
||||
std::move(updateCallback));
|
||||
}
|
||||
ripple->add(point);
|
||||
}
|
||||
|
||||
void Row::elementsStopLastRipple() {
|
||||
if (_acceptRipple) {
|
||||
_acceptRipple->lastStop();
|
||||
}
|
||||
if (_rejectRipple) {
|
||||
_rejectRipple->lastStop();
|
||||
}
|
||||
}
|
||||
|
||||
void Row::elementsPaint(
|
||||
Painter &p,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
int selectedElement) {
|
||||
const auto accept = elementGeometry(kAcceptButton, outerWidth);
|
||||
const auto reject = elementGeometry(kRejectButton, outerWidth);
|
||||
|
||||
const auto over = [&](int element) {
|
||||
return (selectedElement == element);
|
||||
};
|
||||
_delegate->rowPaintAccept(
|
||||
p,
|
||||
accept,
|
||||
_acceptRipple,
|
||||
outerWidth,
|
||||
over(kAcceptButton));
|
||||
_delegate->rowPaintReject(
|
||||
p,
|
||||
reject,
|
||||
_rejectRipple,
|
||||
outerWidth,
|
||||
over(kRejectButton));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class RequestsBoxController::RowHelper final : public RowDelegate {
|
||||
public:
|
||||
explicit RowHelper(bool isGroup);
|
||||
|
||||
[[nodiscard]] QSize rowAcceptButtonSize() override;
|
||||
[[nodiscard]] QSize rowRejectButtonSize() override;
|
||||
void rowPaintAccept(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) override;
|
||||
void rowPaintReject(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) override;
|
||||
|
||||
private:
|
||||
void paintButton(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
const style::RoundButton &st,
|
||||
const Ui::RoundRect &rect,
|
||||
const Ui::RoundRect &rectOver,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
const QString &text,
|
||||
int textWidth,
|
||||
int outerWidth,
|
||||
bool over);
|
||||
|
||||
Ui::RoundRect _acceptRect;
|
||||
Ui::RoundRect _acceptRectOver;
|
||||
Ui::RoundRect _rejectRect;
|
||||
Ui::RoundRect _rejectRectOver;
|
||||
QString _acceptText;
|
||||
QString _rejectText;
|
||||
int _acceptTextWidth = 0;
|
||||
int _rejectTextWidth = 0;
|
||||
|
||||
};
|
||||
|
||||
RequestsBoxController::RowHelper::RowHelper(bool isGroup)
|
||||
: _acceptRect(st::buttonRadius, st::requestsAcceptButton.textBg)
|
||||
, _acceptRectOver(st::buttonRadius, st::requestsAcceptButton.textBgOver)
|
||||
, _rejectRect(st::buttonRadius, st::requestsRejectButton.textBg)
|
||||
, _rejectRectOver(st::buttonRadius, st::requestsRejectButton.textBgOver)
|
||||
, _acceptText(isGroup
|
||||
? tr::lng_group_requests_add(tr::now)
|
||||
: tr::lng_group_requests_add_channel(tr::now))
|
||||
, _rejectText(tr::lng_group_requests_dismiss(tr::now))
|
||||
, _acceptTextWidth(st::requestsAcceptButton.font->width(_acceptText))
|
||||
, _rejectTextWidth(st::requestsRejectButton.font->width(_rejectText)) {
|
||||
}
|
||||
|
||||
RequestsBoxController::RequestsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer)
|
||||
: PeerListController(CreateSearchController(peer))
|
||||
, _navigation(navigation)
|
||||
, _helper(std::make_unique<RowHelper>(!peer->isBroadcast()))
|
||||
, _peer(peer)
|
||||
, _api(&_peer->session().mtp()) {
|
||||
setStyleOverrides(&st::requestsBoxList);
|
||||
subscribeToMigration();
|
||||
}
|
||||
|
||||
RequestsBoxController::~RequestsBoxController() = default;
|
||||
|
||||
void RequestsBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
auto controller = std::make_unique<RequestsBoxController>(
|
||||
navigation,
|
||||
peer->migrateToOrMe());
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
};
|
||||
navigation->parentController()->show(
|
||||
Box<PeerListBox>(std::move(controller), initBox));
|
||||
}
|
||||
|
||||
Main::Session &RequestsBoxController::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
||||
auto RequestsBoxController::CreateSearchController(not_null<PeerData*> peer)
|
||||
-> std::unique_ptr<PeerListSearchController> {
|
||||
return std::make_unique<RequestsBoxSearchController>(peer);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return createRow(user);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RequestsBoxController::prepare() {
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
delegate()->peerListSetTitle(_peer->isBroadcast()
|
||||
? tr::lng_manage_peer_requests_channel()
|
||||
: tr::lng_manage_peer_requests());
|
||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void RequestsBoxController::loadMoreRows() {
|
||||
if (searchController() && searchController()->loadMoreRows()) {
|
||||
return;
|
||||
} else if (_loadRequestId || _allLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First query is small and fast, next loads a lot of rows.
|
||||
const auto limit = _offsetDate ? kPerPage : kFirstPageCount;
|
||||
using Flag = MTPmessages_GetChatInviteImporters::Flag;
|
||||
_loadRequestId = _api.request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(Flag::f_requested),
|
||||
_peer->input,
|
||||
MTPstring(), // link
|
||||
MTPstring(), // q
|
||||
MTP_int(_offsetDate),
|
||||
_offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(),
|
||||
MTP_int(limit)
|
||||
)).done([=](const MTPmessages_ChatInviteImporters &result) {
|
||||
const auto firstLoad = !_offsetDate;
|
||||
_loadRequestId = 0;
|
||||
|
||||
result.match([&](const MTPDmessages_chatInviteImporters &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
const auto &importers = data.vimporters().v;
|
||||
auto &owner = _peer->owner();
|
||||
for (const auto &importer : importers) {
|
||||
importer.match([&](const MTPDchatInviteImporter &data) {
|
||||
_offsetDate = data.vdate().v;
|
||||
_offsetUser = owner.user(data.vuser_id());
|
||||
appendRow(_offsetUser, _offsetDate);
|
||||
});
|
||||
}
|
||||
// To be sure - wait for a whole empty result list.
|
||||
_allLoaded = importers.isEmpty();
|
||||
});
|
||||
|
||||
if (_allLoaded
|
||||
|| (firstLoad && delegate()->peerListFullRowsCount() > 0)) {
|
||||
refreshDescription();
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_loadRequestId = 0;
|
||||
_allLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void RequestsBoxController::refreshDescription() {
|
||||
setDescriptionText((delegate()->peerListFullRowsCount() > 0)
|
||||
? QString()
|
||||
: _peer->isBroadcast()
|
||||
? tr::lng_group_requests_none_channel(tr::now)
|
||||
: tr::lng_group_requests_none(tr::now));
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_navigation->parentController()->show(PrepareShortInfoBox(
|
||||
row->peer(),
|
||||
_navigation));
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowElementClicked(
|
||||
not_null<PeerListRow*> row,
|
||||
int element) {
|
||||
processRequest(row->peer()->asUser(), (element == kAcceptButton));
|
||||
}
|
||||
|
||||
void RequestsBoxController::processRequest(
|
||||
not_null<UserData*> user,
|
||||
bool approved) {
|
||||
const auto remove = [=] {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
refreshDescription();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
static_cast<RequestsBoxSearchController*>(
|
||||
searchController())->removeFromCache(user);
|
||||
};
|
||||
const auto done = crl::guard(this, [=] {
|
||||
remove();
|
||||
if (approved) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = (_peer->isBroadcast()
|
||||
? tr::lng_group_requests_was_added_channel
|
||||
: tr::lng_group_requests_was_added)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(user->name),
|
||||
Ui::Text::WithEntities)
|
||||
});
|
||||
}
|
||||
});
|
||||
const auto fail = crl::guard(this, remove);
|
||||
session().api().inviteLinks().processRequest(
|
||||
_peer,
|
||||
QString(), // link
|
||||
user,
|
||||
approved,
|
||||
done,
|
||||
fail);
|
||||
}
|
||||
|
||||
void RequestsBoxController::appendRow(
|
||||
not_null<UserData*> user,
|
||||
TimeId date) {
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
if (auto row = createRow(user, date)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
setDescriptionText(QString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSize RequestsBoxController::RowHelper::rowAcceptButtonSize() {
|
||||
const auto &st = st::requestsAcceptButton;
|
||||
return {
|
||||
(st.width <= 0) ? (_acceptTextWidth - st.width) : st.width,
|
||||
st.height,
|
||||
};
|
||||
}
|
||||
|
||||
QSize RequestsBoxController::RowHelper::rowRejectButtonSize() {
|
||||
const auto &st = st::requestsRejectButton;
|
||||
return {
|
||||
(st.width <= 0) ? (_rejectTextWidth - st.width) : st.width,
|
||||
st.height,
|
||||
};
|
||||
}
|
||||
|
||||
void RequestsBoxController::RowHelper::rowPaintAccept(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) {
|
||||
paintButton(
|
||||
p,
|
||||
geometry,
|
||||
st::requestsAcceptButton,
|
||||
_acceptRect,
|
||||
_acceptRectOver,
|
||||
ripple,
|
||||
_acceptText,
|
||||
_acceptTextWidth,
|
||||
outerWidth,
|
||||
over);
|
||||
}
|
||||
|
||||
void RequestsBoxController::RowHelper::rowPaintReject(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
int outerWidth,
|
||||
bool over) {
|
||||
paintButton(
|
||||
p,
|
||||
geometry,
|
||||
st::requestsRejectButton,
|
||||
_rejectRect,
|
||||
_rejectRectOver,
|
||||
ripple,
|
||||
_rejectText,
|
||||
_rejectTextWidth,
|
||||
outerWidth,
|
||||
over);
|
||||
}
|
||||
|
||||
void RequestsBoxController::RowHelper::paintButton(
|
||||
Painter &p,
|
||||
QRect geometry,
|
||||
const style::RoundButton &st,
|
||||
const Ui::RoundRect &rect,
|
||||
const Ui::RoundRect &rectOver,
|
||||
std::unique_ptr<Ui::RippleAnimation> &ripple,
|
||||
const QString &text,
|
||||
int textWidth,
|
||||
int outerWidth,
|
||||
bool over) {
|
||||
rect.paint(p, geometry);
|
||||
if (over) {
|
||||
rectOver.paint(p, geometry);
|
||||
}
|
||||
if (ripple) {
|
||||
ripple->paint(p, geometry.x(), geometry.y(), outerWidth);
|
||||
if (ripple->empty()) {
|
||||
ripple = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const auto textLeft = geometry.x()
|
||||
+ ((geometry.width() - textWidth) / 2);
|
||||
const auto textTop = geometry.y() + st.textTop;
|
||||
p.setFont(st.font);
|
||||
p.setPen(over ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(textLeft, textTop, outerWidth, text);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
|
||||
not_null<UserData*> user,
|
||||
TimeId date) {
|
||||
if (!date) {
|
||||
const auto search = static_cast<RequestsBoxSearchController*>(
|
||||
searchController());
|
||||
date = search->dateForUser(user);
|
||||
}
|
||||
return std::make_unique<Row>(_helper.get(), user, date);
|
||||
}
|
||||
|
||||
void RequestsBoxController::subscribeToMigration() {
|
||||
const auto chat = _peer->asChat();
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
SubscribeToMigration(
|
||||
chat,
|
||||
lifetime(),
|
||||
[=](not_null<ChannelData*> channel) { migrate(chat, channel); });
|
||||
}
|
||||
|
||||
void RequestsBoxController::migrate(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<ChannelData*> channel) {
|
||||
_peer = channel;
|
||||
}
|
||||
|
||||
RequestsBoxSearchController::RequestsBoxSearchController(
|
||||
not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&_peer->session().mtp()) {
|
||||
_timer.setCallback([=] { searchOnServer(); });
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::searchQuery(const QString &query) {
|
||||
if (_query != query) {
|
||||
_query = query;
|
||||
_offsetDate = 0;
|
||||
_offsetUser = nullptr;
|
||||
_requestId = 0;
|
||||
_allLoaded = false;
|
||||
if (!_query.isEmpty() && !searchInCache()) {
|
||||
_timer.callOnce(kServerSearchDelay);
|
||||
} else {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::searchOnServer() {
|
||||
Expects(!_query.isEmpty());
|
||||
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
bool RequestsBoxSearchController::isLoading() {
|
||||
return _timer.isActive() || _requestId;
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::removeFromCache(not_null<UserData*> user) {
|
||||
for (auto &entry : _cache) {
|
||||
auto &items = entry.second.items;
|
||||
const auto j = ranges::remove(items, user, &Item::user);
|
||||
if (j != end(items)) {
|
||||
entry.second.requestedCount -= (end(items) - j);
|
||||
items.erase(j, end(items));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
|
||||
if (const auto i = _dates.find(user); i != end(_dates)) {
|
||||
return i->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool RequestsBoxSearchController::searchInCache() {
|
||||
const auto i = _cache.find(_query);
|
||||
if (i != _cache.cend()) {
|
||||
_requestId = 0;
|
||||
searchDone(
|
||||
_requestId,
|
||||
i->second.items,
|
||||
i->second.requestedCount);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RequestsBoxSearchController::loadMoreRows() {
|
||||
if (_query.isEmpty()) {
|
||||
return false;
|
||||
} else if (_allLoaded || isLoading()) {
|
||||
return true;
|
||||
}
|
||||
// For search we request a lot of rows from the first query.
|
||||
// (because we've waited for search request by timer already,
|
||||
// so we don't expect it to be fast, but we want to fill cache).
|
||||
const auto limit = kPerPage;
|
||||
using Flag = MTPmessages_GetChatInviteImporters::Flag;
|
||||
_requestId = _api.request(MTPmessages_GetChatInviteImporters(
|
||||
MTP_flags(Flag::f_requested | Flag::f_q),
|
||||
_peer->input,
|
||||
MTPstring(), // link
|
||||
MTP_string(_query),
|
||||
MTP_int(_offsetDate),
|
||||
_offsetUser ? _offsetUser->inputUser : MTP_inputUserEmpty(),
|
||||
MTP_int(limit)
|
||||
)).done([=](
|
||||
const MTPmessages_ChatInviteImporters &result,
|
||||
mtpRequestId requestId) {
|
||||
auto items = std::vector<Item>();
|
||||
result.match([&](const MTPDmessages_chatInviteImporters &data) {
|
||||
const auto &importers = data.vimporters().v;
|
||||
auto &owner = _peer->owner();
|
||||
owner.processUsers(data.vusers());
|
||||
items.reserve(importers.size());
|
||||
for (const auto &importer : importers) {
|
||||
importer.match([&](const MTPDchatInviteImporter &data) {
|
||||
items.push_back({
|
||||
owner.user(data.vuser_id()),
|
||||
data.vdate().v,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
searchDone(requestId, items, limit);
|
||||
|
||||
auto it = _queries.find(requestId);
|
||||
if (it != _queries.cend()) {
|
||||
const auto &query = it->second.text;
|
||||
if (it->second.offsetDate == 0) {
|
||||
auto &entry = _cache[query];
|
||||
entry.items = std::move(items);
|
||||
entry.requestedCount = limit;
|
||||
}
|
||||
_queries.erase(it);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
if (_requestId == requestId) {
|
||||
_requestId = 0;
|
||||
_allLoaded = true;
|
||||
delegate()->peerListSearchRefreshRows();
|
||||
}
|
||||
}).send();
|
||||
|
||||
auto entry = Query();
|
||||
entry.text = _query;
|
||||
entry.offsetDate = _offsetDate;
|
||||
_queries.emplace(_requestId, entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestsBoxSearchController::searchDone(
|
||||
mtpRequestId requestId,
|
||||
const std::vector<Item> &items,
|
||||
int requestedCount) {
|
||||
if (_requestId != requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
_requestId = 0;
|
||||
if (!_offsetDate) {
|
||||
_dates.clear();
|
||||
}
|
||||
for (const auto &[user, date] : items) {
|
||||
_offsetDate = date;
|
||||
_offsetUser = user;
|
||||
_dates.emplace(user, date);
|
||||
delegate()->peerListSearchAddRow(user);
|
||||
}
|
||||
if (items.size() < requestedCount) {
|
||||
// We want cache to have full information about a query with
|
||||
// small results count (that we don't need the second request).
|
||||
// So we don't wait for empty list unlike the non-search case.
|
||||
_allLoaded = true;
|
||||
}
|
||||
delegate()->peerListSearchRefreshRows();
|
||||
}
|
||||
119
Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h
Normal file
119
Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RippleAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
class RequestsBoxController final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
public:
|
||||
RequestsBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
~RequestsBoxController();
|
||||
|
||||
static void Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createSearchRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
private:
|
||||
class RowHelper;
|
||||
|
||||
static std::unique_ptr<PeerListSearchController> CreateSearchController(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user,
|
||||
TimeId date = 0);
|
||||
|
||||
void appendRow(not_null<UserData*> user, TimeId date);
|
||||
void refreshDescription();
|
||||
void processRequest(not_null<UserData*> user, bool approved);
|
||||
|
||||
void subscribeToMigration();
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const std::unique_ptr<RowHelper> _helper;
|
||||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
UserData *_offsetUser = nullptr;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
bool _allLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
// Members, banned and restricted users server side search.
|
||||
class RequestsBoxSearchController final : public PeerListSearchController {
|
||||
public:
|
||||
RequestsBoxSearchController(not_null<PeerData*> peer);
|
||||
|
||||
void searchQuery(const QString &query) override;
|
||||
bool isLoading() override;
|
||||
bool loadMoreRows() override;
|
||||
|
||||
void removeFromCache(not_null<UserData*> user);
|
||||
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
|
||||
|
||||
private:
|
||||
struct Item {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
};
|
||||
struct CacheEntry {
|
||||
std::vector<Item> items;
|
||||
int requestedCount = 0;
|
||||
};
|
||||
struct Query {
|
||||
QString text;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
};
|
||||
|
||||
void searchOnServer();
|
||||
bool searchInCache();
|
||||
void searchDone(
|
||||
mtpRequestId requestId,
|
||||
const std::vector<Item> &items,
|
||||
int requestedCount);
|
||||
|
||||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::Timer _timer;
|
||||
QString _query;
|
||||
mtpRequestId _requestId = 0;
|
||||
TimeId _offsetDate = 0;
|
||||
UserData *_offsetUser = nullptr;
|
||||
bool _allLoaded = false;
|
||||
base::flat_map<QString, CacheEntry> _cache;
|
||||
base::flat_map<mtpRequestId, Query> _queries;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _dates;
|
||||
|
||||
};
|
||||
@@ -11,9 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_invite_links.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h" // CreateButton.
|
||||
#include "boxes/peers/edit_peer_invite_link.h"
|
||||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
@@ -55,9 +56,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kUsernameCheckTimeout = crl::time(200);
|
||||
constexpr auto kMinUsernameLength = 5;
|
||||
|
||||
class Controller : public base::has_weak_ptr {
|
||||
public:
|
||||
Controller(
|
||||
@@ -419,7 +417,7 @@ void Controller::checkUsernameAvailability() {
|
||||
const auto checking = initial
|
||||
? qsl(".bad.")
|
||||
: getUsernameInput();
|
||||
if (checking.size() < kMinUsernameLength) {
|
||||
if (checking.size() < Ui::EditPeer::kMinUsernameLength) {
|
||||
return;
|
||||
}
|
||||
if (_checkUsernameRequestId) {
|
||||
@@ -496,11 +494,11 @@ void Controller::usernameChanged() {
|
||||
});
|
||||
if (bad) {
|
||||
showUsernameError(tr::lng_create_channel_link_bad_symbols());
|
||||
} else if (username.size() < kMinUsernameLength) {
|
||||
} else if (username.size() < Ui::EditPeer::kMinUsernameLength) {
|
||||
showUsernameError(tr::lng_create_channel_link_too_short());
|
||||
} else {
|
||||
_controls.usernameResult = nullptr;
|
||||
_checkUsernameTimer.callOnce(kUsernameCheckTimeout);
|
||||
_checkUsernameTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
808
Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp
Normal file
808
Telegram/SourceFiles/boxes/peers/peer_short_info_box.cpp
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/peer_short_info_box.h"
|
||||
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/wrap.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "info/profile/info_profile_text.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_player.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kShadowMaxAlpha = 80;
|
||||
constexpr auto kInactiveBarOpacity = 0.5;
|
||||
|
||||
} // namespace
|
||||
|
||||
struct PeerShortInfoCover::CustomLabelStyle {
|
||||
explicit CustomLabelStyle(const style::FlatLabel &original);
|
||||
|
||||
style::complex_color textFg;
|
||||
style::FlatLabel st;
|
||||
float64 opacity = 1.;
|
||||
};
|
||||
|
||||
struct PeerShortInfoCover::Radial {
|
||||
explicit Radial(Fn<void()> &&callback);
|
||||
|
||||
void toggle(bool visible);
|
||||
|
||||
Ui::RadialAnimation radial;
|
||||
Ui::Animations::Simple shownAnimation;
|
||||
Fn<void()> callback;
|
||||
base::Timer showTimer;
|
||||
bool shown = false;
|
||||
};
|
||||
|
||||
PeerShortInfoCover::Radial::Radial(Fn<void()> &&callback)
|
||||
: radial(callback)
|
||||
, callback(callback)
|
||||
, showTimer([=] { toggle(true); }) {
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::Radial::toggle(bool visible) {
|
||||
if (shown == visible) {
|
||||
return;
|
||||
}
|
||||
shown = visible;
|
||||
shownAnimation.start(
|
||||
callback,
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}
|
||||
|
||||
PeerShortInfoCover::CustomLabelStyle::CustomLabelStyle(
|
||||
const style::FlatLabel &original)
|
||||
: textFg([=, c = original.textFg]{
|
||||
auto result = c->c;
|
||||
result.setAlphaF(result.alphaF() * opacity);
|
||||
return result;
|
||||
})
|
||||
, st(original) {
|
||||
st.textFg = textFg.color();
|
||||
}
|
||||
|
||||
PeerShortInfoCover::PeerShortInfoCover(
|
||||
not_null<QWidget*> parent,
|
||||
const style::ShortInfoCover &st,
|
||||
rpl::producer<QString> name,
|
||||
rpl::producer<QString> status,
|
||||
rpl::producer<PeerShortInfoUserpic> userpic,
|
||||
Fn<bool()> videoPaused)
|
||||
: _st(st)
|
||||
, _owned(parent.get())
|
||||
, _widget(_owned.data())
|
||||
, _nameStyle(std::make_unique<CustomLabelStyle>(_st.name))
|
||||
, _name(_widget.get(), std::move(name), _nameStyle->st)
|
||||
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
|
||||
, _status(_widget.get(), std::move(status), _statusStyle->st)
|
||||
, _videoPaused(std::move(videoPaused)) {
|
||||
_widget->setCursor(_cursor);
|
||||
|
||||
_widget->resize(_st.size, _st.size);
|
||||
|
||||
std::move(
|
||||
userpic
|
||||
) | rpl::start_with_next([=](PeerShortInfoUserpic &&value) {
|
||||
applyUserpic(std::move(value));
|
||||
}, lifetime());
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshBarImages();
|
||||
}, lifetime());
|
||||
|
||||
_widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(_widget.get());
|
||||
paint(p);
|
||||
}, lifetime());
|
||||
|
||||
base::install_event_filter(_widget.get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() != QEvent::MouseButtonPress
|
||||
&& e->type() != QEvent::MouseButtonDblClick) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
const auto mouse = static_cast<QMouseEvent*>(e.get());
|
||||
const auto x = mouse->pos().x();
|
||||
if (mouse->button() != Qt::LeftButton) {
|
||||
return base::EventFilterResult::Continue;
|
||||
} else if (/*_index > 0 && */x < _st.size / 3) {
|
||||
_moveRequests.fire(-1);
|
||||
} else if (/*_index + 1 < _count && */x >= _st.size / 3) {
|
||||
_moveRequests.fire(1);
|
||||
}
|
||||
e->accept();
|
||||
return base::EventFilterResult::Cancel;
|
||||
});
|
||||
|
||||
_name->moveToLeft(
|
||||
_st.namePosition.x(),
|
||||
_st.size - _st.namePosition.y() - _name->height(),
|
||||
_st.size);
|
||||
_status->moveToLeft(
|
||||
_st.statusPosition.x(),
|
||||
(_st.size
|
||||
- _st.statusPosition.y()
|
||||
- _status->height()),
|
||||
_st.size);
|
||||
|
||||
_roundedTopImage = QImage(
|
||||
QSize(_st.size, st::boxRadius) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_roundedTopImage.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
_roundedTopImage.fill(Qt::transparent);
|
||||
}
|
||||
|
||||
PeerShortInfoCover::~PeerShortInfoCover() = default;
|
||||
|
||||
not_null<Ui::RpWidget*> PeerShortInfoCover::widget() const {
|
||||
return _widget;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> PeerShortInfoCover::takeOwned() {
|
||||
return std::move(_owned);
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::setScrollTop(int scrollTop) {
|
||||
_scrollTop = scrollTop;
|
||||
_widget->update();
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerShortInfoCover::moveRequests() const {
|
||||
return _moveRequests.events();
|
||||
}
|
||||
|
||||
rpl::lifetime &PeerShortInfoCover::lifetime() {
|
||||
return _widget->lifetime();
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::paint(QPainter &p) {
|
||||
checkStreamedIsStarted();
|
||||
const auto frame = currentVideoFrame();
|
||||
auto paused = _videoPaused && _videoPaused();
|
||||
if (frame.isNull() && _userpicImage.isNull()) {
|
||||
auto image = QImage(
|
||||
_widget->size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::black);
|
||||
Images::prepareRound(
|
||||
image,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
_userpicImage = std::move(image);
|
||||
}
|
||||
|
||||
paintCoverImage(p, frame.isNull() ? _userpicImage : frame);
|
||||
paintBars(p);
|
||||
paintShadow(p);
|
||||
paintRadial(p);
|
||||
if (_videoInstance && _videoInstance->ready() && !paused) {
|
||||
_videoInstance->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
const auto roundedWidth = _st.size;
|
||||
const auto roundedHeight = st::boxRadius;
|
||||
const auto covered = (_st.size - _scrollTop);
|
||||
if (covered <= 0) {
|
||||
return;
|
||||
} else if (!_scrollTop) {
|
||||
p.drawImage(_widget->rect(), image);
|
||||
return;
|
||||
}
|
||||
const auto fill = covered - roundedHeight;
|
||||
const auto top = _widget->height() - fill;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (fill > 0) {
|
||||
p.drawImage(
|
||||
QRect(0, top, roundedWidth, fill),
|
||||
image,
|
||||
QRect(0, top * factor, roundedWidth * factor, fill * factor));
|
||||
}
|
||||
if (covered <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto rounded = std::min(covered, roundedHeight);
|
||||
const auto from = top - rounded;
|
||||
auto q = QPainter(&_roundedTopImage);
|
||||
q.drawImage(
|
||||
QRect(0, 0, roundedWidth, rounded),
|
||||
image,
|
||||
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
||||
q.end();
|
||||
Images::prepareRound(
|
||||
_roundedTopImage,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
p.drawImage(
|
||||
QRect(0, from, roundedWidth, rounded),
|
||||
_roundedTopImage,
|
||||
QRect(0, 0, roundedWidth * factor, rounded * factor));
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::paintBars(QPainter &p) {
|
||||
const auto height = _st.linePadding * 2 + _st.line;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (_shadowTop.isNull()) {
|
||||
_shadowTop = Images::GenerateShadow(height, kShadowMaxAlpha, 0);
|
||||
_shadowTop = _shadowTop.scaled(QSize(_st.size, height) * factor);
|
||||
Images::prepareRound(
|
||||
_shadowTop,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
}
|
||||
const auto shadowRect = QRect(0, _scrollTop, _st.size, height);
|
||||
p.drawImage(
|
||||
shadowRect,
|
||||
_shadowTop,
|
||||
QRect(0, 0, _shadowTop.width(), height * factor));
|
||||
const auto hiddenAt = _st.size - _st.namePosition.y();
|
||||
if (!_smallWidth || _scrollTop >= hiddenAt) {
|
||||
return;
|
||||
}
|
||||
const auto start = _st.linePadding;
|
||||
const auto y = _scrollTop + start;
|
||||
const auto skip = _st.lineSkip;
|
||||
const auto full = (_st.size - 2 * start - (_count - 1) * skip);
|
||||
const auto single = full / float64(_count);
|
||||
const auto masterOpacity = 1. - (_scrollTop / float64(hiddenAt));
|
||||
const auto inactiveOpacity = masterOpacity * kInactiveBarOpacity;
|
||||
for (auto i = 0; i != _count; ++i) {
|
||||
const auto left = start + i * (single + skip);
|
||||
const auto right = left + single;
|
||||
const auto x = qRound(left);
|
||||
const auto small = (qRound(right) == qRound(left) + _smallWidth);
|
||||
const auto width = small ? _smallWidth : _largeWidth;
|
||||
const auto &image = small ? _barSmall : _barLarge;
|
||||
const auto min = 2 * ((_st.line + 1) / 2);
|
||||
const auto minProgress = min / float64(width);
|
||||
const auto videoProgress = (_videoInstance && _videoDuration > 0);
|
||||
const auto progress = (i != _index)
|
||||
? 0.
|
||||
: videoProgress
|
||||
? std::max(_videoPosition / float64(_videoDuration), minProgress)
|
||||
: (_videoInstance ? 0. : 1.);
|
||||
if (progress == 1. && !videoProgress) {
|
||||
p.setOpacity(masterOpacity);
|
||||
p.drawImage(x, y, image);
|
||||
} else {
|
||||
p.setOpacity(inactiveOpacity);
|
||||
p.drawImage(x, y, image);
|
||||
if (progress > 0.) {
|
||||
const auto paint = qRound(progress * width);
|
||||
const auto right = paint / 2;
|
||||
const auto left = paint - right;
|
||||
p.setOpacity(masterOpacity);
|
||||
p.drawImage(
|
||||
QRect(x, y, left, _st.line),
|
||||
image,
|
||||
QRect(0, 0, left * factor, image.height()));
|
||||
p.drawImage(
|
||||
QRect(x + left, y, right, _st.line),
|
||||
image,
|
||||
QRect(left * factor, 0, right * factor, image.height()));
|
||||
}
|
||||
}
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::paintShadow(QPainter &p) {
|
||||
if (_shadowBottom.isNull()) {
|
||||
_shadowBottom = Images::GenerateShadow(
|
||||
_st.shadowHeight,
|
||||
0,
|
||||
kShadowMaxAlpha);
|
||||
}
|
||||
const auto shadowTop = _st.size - _st.shadowHeight;
|
||||
if (_scrollTop >= shadowTop) {
|
||||
_name->hide();
|
||||
_status->hide();
|
||||
return;
|
||||
}
|
||||
const auto opacity = 1. - (_scrollTop / float64(shadowTop));
|
||||
_nameStyle->opacity = opacity;
|
||||
_nameStyle->textFg.refresh();
|
||||
_name->show();
|
||||
_statusStyle->opacity = opacity;
|
||||
_statusStyle->textFg.refresh();
|
||||
_status->show();
|
||||
p.setOpacity(opacity);
|
||||
const auto shadowRect = QRect(
|
||||
0,
|
||||
shadowTop,
|
||||
_st.size,
|
||||
_st.shadowHeight);
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
p.drawImage(
|
||||
shadowRect,
|
||||
_shadowBottom,
|
||||
QRect(
|
||||
0,
|
||||
0,
|
||||
_shadowBottom.width(),
|
||||
_st.shadowHeight * factor));
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::paintRadial(QPainter &p) {
|
||||
const auto infinite = _videoInstance && _videoInstance->waitingShown();
|
||||
if (!_radial && !infinite) {
|
||||
return;
|
||||
}
|
||||
const auto radial = radialRect();
|
||||
const auto line = _st.radialAnimation.thickness;
|
||||
const auto arc = radial.marginsRemoved(
|
||||
{ line, line, line, line });
|
||||
const auto infiniteOpacity = _videoInstance
|
||||
? _videoInstance->waitingOpacity()
|
||||
: 0.;
|
||||
const auto radialState = _radial
|
||||
? _radial->radial.computeState()
|
||||
: Ui::RadialState();
|
||||
if (_radial) {
|
||||
updateRadialState();
|
||||
}
|
||||
const auto radialOpacity = _radial
|
||||
? (_radial->shownAnimation.value(_radial->shown ? 1. : 0.)
|
||||
* radialState.shown)
|
||||
: 0.;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setOpacity(std::max(infiniteOpacity, radialOpacity));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::radialBg);
|
||||
p.drawEllipse(radial);
|
||||
if (radialOpacity > 0.) {
|
||||
p.setOpacity(radialOpacity);
|
||||
auto pen = _st.radialAnimation.color->p;
|
||||
pen.setWidth(line);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
p.setPen(pen);
|
||||
p.drawArc(arc, radialState.arcFrom, radialState.arcLength);
|
||||
}
|
||||
if (infinite) {
|
||||
p.setOpacity(1.);
|
||||
Ui::InfiniteRadialAnimation::Draw(
|
||||
p,
|
||||
_videoInstance->waitingState(),
|
||||
arc.topLeft(),
|
||||
arc.size(),
|
||||
_st.size,
|
||||
_st.radialAnimation.color,
|
||||
line);
|
||||
}
|
||||
}
|
||||
|
||||
QImage PeerShortInfoCover::currentVideoFrame() const {
|
||||
const auto size = QSize(_st.size, _st.size);
|
||||
const auto request = Media::Streaming::FrameRequest{
|
||||
.resize = size * style::DevicePixelRatio(),
|
||||
.outer = size,
|
||||
.radius = ImageRoundRadius::Small,
|
||||
.corners = RectPart::TopLeft | RectPart::TopRight,
|
||||
};
|
||||
return (_videoInstance
|
||||
&& _videoInstance->player().ready()
|
||||
&& !_videoInstance->player().videoSize().isEmpty())
|
||||
? _videoInstance->frame(request)
|
||||
: QImage();
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::applyUserpic(PeerShortInfoUserpic &&value) {
|
||||
if (_index != value.index) {
|
||||
_index = value.index;
|
||||
_widget->update();
|
||||
}
|
||||
if (_count != value.count) {
|
||||
_count = value.count;
|
||||
refreshCoverCursor();
|
||||
refreshBarImages();
|
||||
_widget->update();
|
||||
}
|
||||
if (value.photo.isNull()) {
|
||||
const auto videoChanged = _videoInstance
|
||||
? (_videoInstance->shared() != value.videoDocument)
|
||||
: (value.videoDocument != nullptr);
|
||||
const auto frame = videoChanged ? currentVideoFrame() : QImage();
|
||||
if (!frame.isNull()) {
|
||||
_userpicImage = frame;
|
||||
}
|
||||
} else if (_userpicImage.cacheKey() != value.photo.cacheKey()) {
|
||||
_userpicImage = std::move(value.photo);
|
||||
_widget->update();
|
||||
}
|
||||
if (!value.videoDocument) {
|
||||
clearVideo();
|
||||
} else if (!_videoInstance
|
||||
|| _videoInstance->shared() != value.videoDocument) {
|
||||
using namespace Media::Streaming;
|
||||
_videoInstance = std::make_unique<Instance>(
|
||||
std::move(value.videoDocument),
|
||||
[=] { videoWaiting(); });
|
||||
_videoStartPosition = value.videoStartPosition;
|
||||
_videoInstance->lockPlayer();
|
||||
_videoInstance->player().updates(
|
||||
) | rpl::start_with_next_error([=](Update &&update) {
|
||||
handleStreamingUpdate(std::move(update));
|
||||
}, [=](Error &&error) {
|
||||
handleStreamingError(std::move(error));
|
||||
}, _videoInstance->lifetime());
|
||||
if (_videoInstance->ready()) {
|
||||
streamingReady(base::duplicate(_videoInstance->info()));
|
||||
}
|
||||
if (!_videoInstance->valid()) {
|
||||
clearVideo();
|
||||
}
|
||||
}
|
||||
_photoLoadingProgress = value.photoLoadingProgress;
|
||||
updateRadialState();
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::updateRadialState() {
|
||||
const auto progress = _videoInstance ? 1. : _photoLoadingProgress;
|
||||
if (_radial) {
|
||||
_radial->radial.update(progress, (progress == 1.), crl::now());
|
||||
}
|
||||
_widget->update(radialRect());
|
||||
|
||||
if (progress == 1.) {
|
||||
if (!_radial) {
|
||||
return;
|
||||
}
|
||||
_radial->showTimer.cancel();
|
||||
_radial->toggle(false);
|
||||
if (!_radial->shownAnimation.animating()) {
|
||||
_radial = nullptr;
|
||||
}
|
||||
return;
|
||||
} else if (!_radial) {
|
||||
_radial = std::make_unique<Radial>([=] { updateRadialState(); });
|
||||
_radial->radial.update(progress, false, crl::now());
|
||||
_radial->showTimer.callOnce(st::fadeWrapDuration);
|
||||
return;
|
||||
} else if (!_radial->showTimer.isActive()) {
|
||||
_radial->toggle(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::clearVideo() {
|
||||
_videoInstance = nullptr;
|
||||
_videoStartPosition = _videoPosition = _videoDuration = 0;
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::checkStreamedIsStarted() {
|
||||
if (!_videoInstance) {
|
||||
return;
|
||||
} else if (_videoInstance->paused()) {
|
||||
_videoInstance->resume();
|
||||
}
|
||||
if (!_videoInstance
|
||||
|| _videoInstance->active()
|
||||
|| _videoInstance->failed()) {
|
||||
return;
|
||||
}
|
||||
auto options = Media::Streaming::PlaybackOptions();
|
||||
options.position = _videoStartPosition;
|
||||
options.mode = Media::Streaming::Mode::Video;
|
||||
options.loop = true;
|
||||
_videoInstance->play(options);
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::handleStreamingUpdate(
|
||||
Media::Streaming::Update &&update) {
|
||||
using namespace Media::Streaming;
|
||||
|
||||
v::match(update.data, [&](Information &update) {
|
||||
streamingReady(std::move(update));
|
||||
}, [&](const PreloadedVideo &update) {
|
||||
}, [&](const UpdateVideo &update) {
|
||||
_videoPosition = update.position;
|
||||
_widget->update();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
}, [&](const UpdateAudio &update) {
|
||||
}, [&](const WaitingForData &update) {
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
});
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::handleStreamingError(
|
||||
Media::Streaming::Error &&error) {
|
||||
//_streamedPhoto->setVideoPlaybackFailed();
|
||||
//_streamedPhoto = nullptr;
|
||||
clearVideo();
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::streamingReady(Media::Streaming::Information &&info) {
|
||||
_videoPosition = info.video.state.position;
|
||||
_videoDuration = info.video.state.duration;
|
||||
_widget->update();
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::refreshCoverCursor() {
|
||||
const auto cursor = (_count > 1)
|
||||
? style::cur_pointer
|
||||
: style::cur_default;
|
||||
if (_cursor != cursor) {
|
||||
_cursor = cursor;
|
||||
_widget->setCursor(_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::refreshBarImages() {
|
||||
if (_count < 2) {
|
||||
_smallWidth = _largeWidth = 0;
|
||||
_barSmall = _barLarge = QImage();
|
||||
return;
|
||||
}
|
||||
const auto width = _st.size - 2 * _st.linePadding;
|
||||
_smallWidth = (width - (_count - 1) * _st.lineSkip) / _count;
|
||||
if (_smallWidth < _st.line) {
|
||||
_smallWidth = _largeWidth = 0;
|
||||
_barSmall = _barLarge = QImage();
|
||||
return;
|
||||
}
|
||||
_largeWidth = _smallWidth + 1;
|
||||
const auto makeBar = [&](int size) {
|
||||
const auto radius = _st.line / 2.;
|
||||
auto result = QImage(
|
||||
QSize(size, _st.line) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
result.fill(Qt::transparent);
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::groupCallVideoTextFg);
|
||||
p.drawRoundedRect(0, 0, size, _st.line, radius, radius);
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
};
|
||||
_barSmall = makeBar(_smallWidth);
|
||||
_barLarge = makeBar(_largeWidth);
|
||||
}
|
||||
|
||||
QRect PeerShortInfoCover::radialRect() const {
|
||||
const auto cover = _widget->rect();
|
||||
const auto size = st::boxLoadingSize;
|
||||
return QRect(
|
||||
cover.x() + (cover.width() - size) / 2,
|
||||
cover.y() + (cover.height() - size) / 2,
|
||||
size,
|
||||
size);
|
||||
}
|
||||
|
||||
void PeerShortInfoCover::videoWaiting() {
|
||||
if (!anim::Disabled()) {
|
||||
_widget->update(radialRect());
|
||||
}
|
||||
}
|
||||
|
||||
PeerShortInfoBox::PeerShortInfoBox(
|
||||
QWidget*,
|
||||
PeerShortInfoType type,
|
||||
rpl::producer<PeerShortInfoFields> fields,
|
||||
rpl::producer<QString> status,
|
||||
rpl::producer<PeerShortInfoUserpic> userpic,
|
||||
Fn<bool()> videoPaused)
|
||||
: _type(type)
|
||||
, _fields(std::move(fields))
|
||||
, _topRoundBackground(this)
|
||||
, _scroll(this, st::shortInfoScroll)
|
||||
, _rows(
|
||||
_scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
_scroll.data())))
|
||||
, _cover(
|
||||
_rows.get(),
|
||||
st::shortInfoCover,
|
||||
nameValue(),
|
||||
std::move(status),
|
||||
std::move(userpic),
|
||||
std::move(videoPaused)) {
|
||||
_rows->add(_cover.takeOwned());
|
||||
|
||||
_scroll->scrolls(
|
||||
) | rpl::start_with_next([=] {
|
||||
_cover.setScrollTop(_scroll->scrollTop());
|
||||
}, _cover.lifetime());
|
||||
}
|
||||
|
||||
PeerShortInfoBox::~PeerShortInfoBox() = default;
|
||||
|
||||
rpl::producer<> PeerShortInfoBox::openRequests() const {
|
||||
return _openRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerShortInfoBox::moveRequests() const {
|
||||
return _cover.moveRequests();
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::prepare() {
|
||||
addButton(tr::lng_close(), [=] { closeBox(); });
|
||||
|
||||
// Perhaps a new lang key should be added for opening a group.
|
||||
addLeftButton((_type == PeerShortInfoType::User)
|
||||
? tr::lng_profile_send_message()
|
||||
: (_type == PeerShortInfoType::Group)
|
||||
? tr::lng_view_button_group()
|
||||
: tr::lng_profile_view_channel(), [=] { _openRequests.fire({}); });
|
||||
|
||||
prepareRows();
|
||||
|
||||
setNoContentMargin(true);
|
||||
|
||||
_topRoundBackground->resize(st::shortInfoWidth, st::boxRadius);
|
||||
_topRoundBackground->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (const auto use = fillRoundedTopHeight()) {
|
||||
const auto width = _topRoundBackground->width();
|
||||
const auto top = _topRoundBackground->height() - use;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
QPainter(_topRoundBackground.data()).drawImage(
|
||||
QRect(0, top, width, use),
|
||||
_roundedTop,
|
||||
QRect(0, top * factor, width * factor, use * factor));
|
||||
}
|
||||
}, _topRoundBackground->lifetime());
|
||||
|
||||
_roundedTop = QImage(
|
||||
_topRoundBackground->size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
refreshRoundedTopImage(getDelegate()->style().bg->c);
|
||||
|
||||
setDimensionsToContent(st::shortInfoWidth, _rows);
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::prepareRows() {
|
||||
using namespace Info::Profile;
|
||||
|
||||
auto addInfoLineGeneric = [&](
|
||||
rpl::producer<QString> &&label,
|
||||
rpl::producer<TextWithEntities> &&text,
|
||||
const style::FlatLabel &textSt = st::infoLabeled) {
|
||||
auto line = CreateTextWithLabel(
|
||||
_rows,
|
||||
rpl::duplicate(label) | Ui::Text::ToWithEntities(),
|
||||
rpl::duplicate(text),
|
||||
textSt,
|
||||
st::shortInfoLabeledPadding);
|
||||
_rows->add(object_ptr<Ui::OverrideMargins>(
|
||||
_rows.get(),
|
||||
std::move(line.wrap)));
|
||||
|
||||
rpl::combine(
|
||||
std::move(label),
|
||||
std::move(text)
|
||||
) | rpl::start_with_next([=] {
|
||||
_rows->resizeToWidth(st::shortInfoWidth);
|
||||
}, _rows->lifetime());
|
||||
|
||||
//line.text->setClickHandlerFilter(infoClickFilter);
|
||||
return line.text;
|
||||
};
|
||||
auto addInfoLine = [&](
|
||||
rpl::producer<QString> &&label,
|
||||
rpl::producer<TextWithEntities> &&text,
|
||||
const style::FlatLabel &textSt = st::infoLabeled) {
|
||||
return addInfoLineGeneric(
|
||||
std::move(label),
|
||||
std::move(text),
|
||||
textSt);
|
||||
};
|
||||
auto addInfoOneLine = [&](
|
||||
rpl::producer<QString> &&label,
|
||||
rpl::producer<TextWithEntities> &&text,
|
||||
const QString &contextCopyText) {
|
||||
auto result = addInfoLine(
|
||||
std::move(label),
|
||||
std::move(text),
|
||||
st::infoLabeledOneLine);
|
||||
result->setDoubleClickSelectsParagraph(true);
|
||||
result->setContextCopyText(contextCopyText);
|
||||
return result;
|
||||
};
|
||||
addInfoOneLine(
|
||||
tr::lng_info_link_label(),
|
||||
linkValue(),
|
||||
tr::lng_context_copy_link(tr::now));
|
||||
addInfoOneLine(
|
||||
tr::lng_info_mobile_label(),
|
||||
phoneValue() | Ui::Text::ToWithEntities(),
|
||||
tr::lng_profile_copy_phone(tr::now));
|
||||
auto label = _fields.current().isBio
|
||||
? tr::lng_info_bio_label()
|
||||
: tr::lng_info_about_label();
|
||||
addInfoLine(std::move(label), aboutValue());
|
||||
addInfoOneLine(
|
||||
tr::lng_info_username_label(),
|
||||
usernameValue() | Ui::Text::ToWithEntities(),
|
||||
tr::lng_context_copy_mention(tr::now));
|
||||
}
|
||||
|
||||
RectParts PeerShortInfoBox::customCornersFilling() {
|
||||
return RectPart::FullTop;
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_rows->resizeToWidth(st::shortInfoWidth);
|
||||
_scroll->resize(st::shortInfoWidth, height());
|
||||
_scroll->move(0, 0);
|
||||
_topRoundBackground->move(0, 0);
|
||||
}
|
||||
|
||||
int PeerShortInfoBox::fillRoundedTopHeight() {
|
||||
const auto roundedHeight = _topRoundBackground->height();
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
const auto covered = (st::shortInfoWidth - scrollTop);
|
||||
if (covered >= roundedHeight) {
|
||||
return 0;
|
||||
}
|
||||
const auto &color = getDelegate()->style().bg->c;
|
||||
if (_roundedTopColor != color) {
|
||||
refreshRoundedTopImage(color);
|
||||
}
|
||||
return roundedHeight - covered;
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::refreshRoundedTopImage(const QColor &color) {
|
||||
_roundedTopColor = color;
|
||||
_roundedTop.fill(color);
|
||||
Images::prepareRound(
|
||||
_roundedTop,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
}
|
||||
|
||||
rpl::producer<QString> PeerShortInfoBox::nameValue() const {
|
||||
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return fields.name;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
|
||||
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return Ui::Text::Link(fields.link, fields.link);
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<QString> PeerShortInfoBox::phoneValue() const {
|
||||
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return fields.phone;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<QString> PeerShortInfoBox::usernameValue() const {
|
||||
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return fields.username;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::aboutValue() const {
|
||||
return _fields.value() | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return fields.about;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
180
Telegram/SourceFiles/boxes/peers/peer_short_info_box.h
Normal file
180
Telegram/SourceFiles/boxes/peers/peer_short_info_box.h
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace style {
|
||||
struct ShortInfoCover;
|
||||
} // namespace style
|
||||
|
||||
namespace Media::Streaming {
|
||||
class Document;
|
||||
class Instance;
|
||||
struct Update;
|
||||
enum class Error;
|
||||
struct Information;
|
||||
} // namespace Media::Streaming
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
enum class PeerShortInfoType {
|
||||
User,
|
||||
Group,
|
||||
Channel,
|
||||
};
|
||||
|
||||
struct PeerShortInfoFields {
|
||||
QString name;
|
||||
QString phone;
|
||||
QString link;
|
||||
TextWithEntities about;
|
||||
QString username;
|
||||
bool isBio = false;
|
||||
};
|
||||
|
||||
struct PeerShortInfoUserpic {
|
||||
int index = 0;
|
||||
int count = 0;
|
||||
|
||||
QImage photo;
|
||||
float64 photoLoadingProgress = 0.;
|
||||
std::shared_ptr<Media::Streaming::Document> videoDocument;
|
||||
crl::time videoStartPosition = 0;
|
||||
};
|
||||
|
||||
class PeerShortInfoCover final {
|
||||
public:
|
||||
PeerShortInfoCover(
|
||||
not_null<QWidget*> parent,
|
||||
const style::ShortInfoCover &st,
|
||||
rpl::producer<QString> name,
|
||||
rpl::producer<QString> status,
|
||||
rpl::producer<PeerShortInfoUserpic> userpic,
|
||||
Fn<bool()> videoPaused);
|
||||
~PeerShortInfoCover();
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> takeOwned();
|
||||
|
||||
void setScrollTop(int scrollTop);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> moveRequests() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
struct CustomLabelStyle;
|
||||
struct Radial;
|
||||
|
||||
void paint(QPainter &p);
|
||||
void paintCoverImage(QPainter &p, const QImage &image);
|
||||
void paintBars(QPainter &p);
|
||||
void paintShadow(QPainter &p);
|
||||
void paintRadial(QPainter &p);
|
||||
|
||||
[[nodiscard]] QImage currentVideoFrame() const;
|
||||
|
||||
void applyUserpic(PeerShortInfoUserpic &&value);
|
||||
[[nodiscard]] QRect radialRect() const;
|
||||
|
||||
void videoWaiting();
|
||||
void checkStreamedIsStarted();
|
||||
void handleStreamingUpdate(Media::Streaming::Update &&update);
|
||||
void handleStreamingError(Media::Streaming::Error &&error);
|
||||
void streamingReady(Media::Streaming::Information &&info);
|
||||
void clearVideo();
|
||||
|
||||
void updateRadialState();
|
||||
void refreshCoverCursor();
|
||||
void refreshBarImages();
|
||||
|
||||
const style::ShortInfoCover &_st;
|
||||
|
||||
object_ptr<Ui::RpWidget> _owned;
|
||||
const not_null<Ui::RpWidget*> _widget;
|
||||
std::unique_ptr<CustomLabelStyle> _nameStyle;
|
||||
object_ptr<Ui::FlatLabel> _name;
|
||||
std::unique_ptr<CustomLabelStyle> _statusStyle;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
|
||||
QImage _userpicImage;
|
||||
QImage _roundedTopImage;
|
||||
QImage _barSmall;
|
||||
QImage _barLarge;
|
||||
QImage _shadowTop;
|
||||
int _scrollTop = 0;
|
||||
int _smallWidth = 0;
|
||||
int _largeWidth = 0;
|
||||
int _index = 0;
|
||||
int _count = 0;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
|
||||
std::unique_ptr<Media::Streaming::Instance> _videoInstance;
|
||||
crl::time _videoStartPosition = 0;
|
||||
crl::time _videoPosition = 0;
|
||||
crl::time _videoDuration = 0;
|
||||
Fn<bool()> _videoPaused;
|
||||
QImage _shadowBottom;
|
||||
|
||||
std::unique_ptr<Radial> _radial;
|
||||
float64 _photoLoadingProgress = 0.;
|
||||
|
||||
rpl::event_stream<int> _moveRequests;
|
||||
|
||||
};
|
||||
|
||||
class PeerShortInfoBox final : public Ui::BoxContent {
|
||||
public:
|
||||
PeerShortInfoBox(
|
||||
QWidget*,
|
||||
PeerShortInfoType type,
|
||||
rpl::producer<PeerShortInfoFields> fields,
|
||||
rpl::producer<QString> status,
|
||||
rpl::producer<PeerShortInfoUserpic> userpic,
|
||||
Fn<bool()> videoPaused);
|
||||
~PeerShortInfoBox();
|
||||
|
||||
[[nodiscard]] rpl::producer<> openRequests() const;
|
||||
[[nodiscard]] rpl::producer<int> moveRequests() const;
|
||||
|
||||
private:
|
||||
void prepare() override;
|
||||
void prepareRows();
|
||||
RectParts customCornersFilling() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void refreshRoundedTopImage(const QColor &color);
|
||||
int fillRoundedTopHeight();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> nameValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> phoneValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> usernameValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> aboutValue() const;
|
||||
|
||||
const PeerShortInfoType _type = PeerShortInfoType::User;
|
||||
|
||||
rpl::variable<PeerShortInfoFields> _fields;
|
||||
|
||||
QColor _roundedTopColor;
|
||||
QImage _roundedTop;
|
||||
|
||||
object_ptr<Ui::RpWidget> _topRoundBackground;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
not_null<Ui::VerticalLayout*> _rows;
|
||||
PeerShortInfoCover _cover;
|
||||
|
||||
rpl::event_stream<> _openRequests;
|
||||
|
||||
};
|
||||
465
Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
Normal file
465
Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
|
||||
#include "boxes/peers/peer_short_info_box.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_user_photos.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kOverviewLimit = 48;
|
||||
|
||||
struct UserpicState {
|
||||
PeerShortInfoUserpic current;
|
||||
std::optional<UserPhotosSlice> userSlice;
|
||||
PhotoId userpicPhotoId = PeerData::kUnknownPhotoId;
|
||||
std::shared_ptr<Data::CloudImageView> userpicView;
|
||||
std::shared_ptr<Data::PhotoMedia> photoView;
|
||||
std::vector<std::shared_ptr<Data::PhotoMedia>> photoPreloads;
|
||||
InMemoryKey userpicKey;
|
||||
PhotoId photoId = PeerData::kUnknownPhotoId;
|
||||
bool waitingFull = false;
|
||||
bool waitingLoad = false;
|
||||
};
|
||||
|
||||
void GenerateImage(
|
||||
not_null<UserpicState*> state,
|
||||
QImage image,
|
||||
bool blurred = false) {
|
||||
using namespace Images;
|
||||
const auto size = st::shortInfoWidth;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto options = Option::Smooth
|
||||
| Option::RoundedSmall
|
||||
| Option::RoundedTopLeft
|
||||
| Option::RoundedTopRight
|
||||
| (blurred ? Option::Blurred : Option());
|
||||
state->current.photo = Images::prepare(
|
||||
std::move(image),
|
||||
size * factor,
|
||||
size * factor,
|
||||
options,
|
||||
size,
|
||||
size);
|
||||
}
|
||||
|
||||
void GenerateImage(
|
||||
not_null<UserpicState*> state,
|
||||
not_null<Image*> image,
|
||||
bool blurred = false) {
|
||||
GenerateImage(state, image->original(), blurred);
|
||||
}
|
||||
|
||||
void ProcessUserpic(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserpicState*> state) {
|
||||
state->current.videoDocument = nullptr;
|
||||
state->userpicKey = peer->userpicUniqueKey(state->userpicView);
|
||||
if (!state->userpicView) {
|
||||
GenerateImage(
|
||||
state,
|
||||
peer->generateUserpicImage(
|
||||
state->userpicView,
|
||||
st::shortInfoWidth * style::DevicePixelRatio(),
|
||||
ImageRoundRadius::None),
|
||||
false);
|
||||
state->current.photoLoadingProgress = 1.;
|
||||
state->photoView = nullptr;
|
||||
return;
|
||||
}
|
||||
peer->loadUserpic();
|
||||
const auto image = state->userpicView->image();
|
||||
if (!image) {
|
||||
state->current.photoLoadingProgress = 0.;
|
||||
state->current.photo = QImage();
|
||||
state->waitingLoad = true;
|
||||
return;
|
||||
}
|
||||
GenerateImage(state, image, true);
|
||||
state->current.photoLoadingProgress = peer->userpicPhotoId() ? 0. : 1.;
|
||||
state->photoView = nullptr;
|
||||
}
|
||||
|
||||
void Preload(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserpicState*> state) {
|
||||
auto taken = base::take(state->photoPreloads);
|
||||
if (state->userSlice && state->userSlice->size() > 0) {
|
||||
const auto preload = [&](int index) {
|
||||
const auto photo = peer->owner().photo(
|
||||
(*state->userSlice)[index]);
|
||||
const auto current = (peer->userpicPhotoId() == photo->id);
|
||||
const auto origin = current
|
||||
? peer->userpicPhotoOrigin()
|
||||
: Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id);
|
||||
state->photoPreloads.push_back(photo->createMediaView());
|
||||
if (photo->hasVideo()) {
|
||||
state->photoPreloads.back()->videoWanted(origin);
|
||||
} else {
|
||||
state->photoPreloads.back()->wanted(
|
||||
Data::PhotoSize::Large,
|
||||
origin);
|
||||
}
|
||||
};
|
||||
const auto skip = (state->userSlice->size() == state->current.count)
|
||||
? 0
|
||||
: 1;
|
||||
if (state->current.index - skip > 0) {
|
||||
preload(state->current.index - skip - 1);
|
||||
} else if (!state->current.index && state->current.count > 1) {
|
||||
preload(state->userSlice->size() - 1);
|
||||
}
|
||||
if (state->current.index - skip + 1 < state->userSlice->size()) {
|
||||
preload(state->current.index - skip + 1);
|
||||
} else if (!skip && state->current.index > 0) {
|
||||
preload(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessFullPhoto(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserpicState*> state,
|
||||
not_null<PhotoData*> photo) {
|
||||
using PhotoSize = Data::PhotoSize;
|
||||
const auto current = (peer->userpicPhotoId() == photo->id);
|
||||
const auto video = photo->hasVideo();
|
||||
const auto originCurrent = peer->userpicPhotoOrigin();
|
||||
const auto originOther = peer->isUser()
|
||||
? Data::FileOriginUserPhoto(peerToUser(peer->id), photo->id)
|
||||
: originCurrent;
|
||||
const auto origin = current ? originCurrent : originOther;
|
||||
const auto was = base::take(state->current.videoDocument);
|
||||
const auto view = photo->createMediaView();
|
||||
if (!video) {
|
||||
view->wanted(PhotoSize::Large, origin);
|
||||
}
|
||||
if (const auto image = view->image(PhotoSize::Large)) {
|
||||
GenerateImage(state, image);
|
||||
Preload(peer, state);
|
||||
state->photoView = nullptr;
|
||||
state->current.photoLoadingProgress = 1.;
|
||||
} else {
|
||||
if (const auto thumbnail = view->image(PhotoSize::Thumbnail)) {
|
||||
GenerateImage(state, thumbnail, true);
|
||||
} else if (const auto small = view->image(PhotoSize::Small)) {
|
||||
GenerateImage(state, small, true);
|
||||
} else {
|
||||
if (current) {
|
||||
ProcessUserpic(peer, state);
|
||||
}
|
||||
if (!current || state->current.photo.isNull()) {
|
||||
if (const auto blurred = view->thumbnailInline()) {
|
||||
GenerateImage(state, blurred, true);
|
||||
} else {
|
||||
state->current.photo = QImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
state->waitingLoad = !video;
|
||||
state->photoView = view;
|
||||
state->current.photoLoadingProgress = photo->progress();
|
||||
}
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
state->current.videoDocument = peer->owner().streaming().sharedDocument(
|
||||
photo,
|
||||
origin);
|
||||
state->current.videoStartPosition = photo->videoStartPosition();
|
||||
state->photoView = nullptr;
|
||||
state->current.photoLoadingProgress = 1.;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerShortInfoFields> FieldsValue(
|
||||
not_null<PeerData*> peer) {
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
(UpdateFlag::Name
|
||||
| UpdateFlag::PhoneNumber
|
||||
| UpdateFlag::Username
|
||||
| UpdateFlag::About)
|
||||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
const auto username = peer->userName();
|
||||
return PeerShortInfoFields{
|
||||
.name = peer->name,
|
||||
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
|
||||
.link = ((user || username.isEmpty())
|
||||
? QString()
|
||||
: peer->session().createInternalLinkFull(username)),
|
||||
.about = Info::Profile::AboutWithEntities(peer, peer->about()),
|
||||
.username = ((user && !username.isEmpty())
|
||||
? ('@' + username)
|
||||
: QString()),
|
||||
.isBio = (user && !user->isBot()),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> StatusValue(not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
const auto now = base::unixtime::now();
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto timer = lifetime.make_state<base::Timer>();
|
||||
const auto push = [=] {
|
||||
consumer.put_next(Data::OnlineText(user, now));
|
||||
timer->callOnce(Data::OnlineChangeTimeout(user, now));
|
||||
};
|
||||
timer->setCallback(push);
|
||||
push();
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Members
|
||||
) | rpl::map([=] {
|
||||
const auto chat = peer->asChat();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto count = std::max({
|
||||
chat ? chat->count : channel->membersCount(),
|
||||
chat ? int(chat->participants.size()) : 0,
|
||||
0,
|
||||
});
|
||||
return (chat && !chat->amIn())
|
||||
? tr::lng_chat_status_unaccessible(tr::now)
|
||||
: (count > 0)
|
||||
? ((channel && channel->isBroadcast())
|
||||
? tr::lng_chat_status_subscribers(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
count)
|
||||
: tr::lng_chat_status_members(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
count))
|
||||
: ((channel && channel->isBroadcast())
|
||||
? tr::lng_channel_status(tr::now)
|
||||
: tr::lng_group_status(tr::now));
|
||||
});
|
||||
}
|
||||
|
||||
void ValidatePhotoId(
|
||||
not_null<UserpicState*> state,
|
||||
PhotoId oldUserpicPhotoId) {
|
||||
if (state->userSlice) {
|
||||
const auto count = state->userSlice->size();
|
||||
const auto hasOld = state->userSlice->indexOf(
|
||||
oldUserpicPhotoId).has_value();
|
||||
const auto hasNew = state->userSlice->indexOf(
|
||||
state->userpicPhotoId).has_value();
|
||||
const auto shift = (hasNew ? 0 : 1);
|
||||
const auto fullCount = count + shift;
|
||||
state->current.count = fullCount;
|
||||
if (hasOld && !hasNew && state->current.index + 1 < fullCount) {
|
||||
++state->current.index;
|
||||
} else if (!hasOld && hasNew && state->current.index > 0) {
|
||||
--state->current.index;
|
||||
}
|
||||
const auto index = state->current.index;
|
||||
if (!index || index >= fullCount) {
|
||||
state->current.index = 0;
|
||||
state->photoId = state->userpicPhotoId;
|
||||
} else {
|
||||
state->photoId = (*state->userSlice)[index - shift];
|
||||
}
|
||||
} else {
|
||||
state->photoId = state->userpicPhotoId;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessCurrent(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserpicState*> state) {
|
||||
const auto userpicPhotoId = peer->userpicPhotoId();
|
||||
const auto userpicPhoto = (userpicPhotoId
|
||||
&& (userpicPhotoId != PeerData::kUnknownPhotoId)
|
||||
&& (state->userpicPhotoId != userpicPhotoId))
|
||||
? peer->owner().photo(userpicPhotoId).get()
|
||||
: (state->photoId == userpicPhotoId && state->photoView)
|
||||
? state->photoView->owner().get()
|
||||
: nullptr;
|
||||
state->waitingFull = (state->userpicPhotoId != userpicPhotoId)
|
||||
&& ((userpicPhotoId == PeerData::kUnknownPhotoId)
|
||||
|| (userpicPhotoId && userpicPhoto->isNull()));
|
||||
if (state->waitingFull) {
|
||||
peer->updateFullForced();
|
||||
}
|
||||
const auto oldUserpicPhotoId = state->waitingFull
|
||||
? state->userpicPhotoId
|
||||
: std::exchange(state->userpicPhotoId, userpicPhotoId);
|
||||
const auto changedUserpic = (state->userpicKey
|
||||
!= peer->userpicUniqueKey(state->userpicView));
|
||||
|
||||
const auto wasIndex = state->current.index;
|
||||
const auto wasCount = state->current.count;
|
||||
const auto wasPhotoId = state->photoId;
|
||||
ValidatePhotoId(state, oldUserpicPhotoId);
|
||||
const auto changedInSlice = (state->current.index != wasIndex)
|
||||
|| (state->current.count != wasCount);
|
||||
const auto changedPhotoId = (state->photoId != wasPhotoId);
|
||||
const auto photo = (state->photoId == state->userpicPhotoId
|
||||
&& userpicPhoto)
|
||||
? userpicPhoto
|
||||
: (state->photoId
|
||||
&& (state->photoId != PeerData::kUnknownPhotoId)
|
||||
&& changedPhotoId)
|
||||
? peer->owner().photo(state->photoId).get()
|
||||
: state->photoView
|
||||
? state->photoView->owner().get()
|
||||
: nullptr;
|
||||
state->waitingLoad = false;
|
||||
if (!changedPhotoId
|
||||
&& (state->current.index > 0 || !changedUserpic)
|
||||
&& !state->photoView
|
||||
&& (!state->current.photo.isNull()
|
||||
|| state->current.videoDocument)) {
|
||||
return changedInSlice;
|
||||
} else if (photo && !photo->isNull()) {
|
||||
ProcessFullPhoto(peer, state, photo);
|
||||
} else if (state->current.index > 0) {
|
||||
return changedInSlice;
|
||||
} else {
|
||||
ProcessUserpic(peer, state);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] PreparedShortInfoUserpic UserpicValue(
|
||||
not_null<PeerData*> peer) {
|
||||
const auto moveRequests = std::make_shared<rpl::event_stream<int>>();
|
||||
auto move = [=](int shift) {
|
||||
moveRequests->fire_copy(shift);
|
||||
};
|
||||
auto value = [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto state = lifetime.make_state<UserpicState>();
|
||||
const auto push = [=](bool force = false) {
|
||||
if (ProcessCurrent(peer, state) || force) {
|
||||
consumer.put_next_copy(state->current);
|
||||
}
|
||||
};
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
UpdateFlag::Photo | UpdateFlag::FullInfo
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.flags & UpdateFlag::Photo) || state->waitingFull;
|
||||
}) | rpl::start_with_next([=] {
|
||||
push();
|
||||
}, lifetime);
|
||||
|
||||
if (const auto user = peer->asUser()) {
|
||||
UserPhotosReversedViewer(
|
||||
&peer->session(),
|
||||
UserPhotosSlice::Key(peerToUser(user->id), PhotoId()),
|
||||
kOverviewLimit,
|
||||
kOverviewLimit
|
||||
) | rpl::start_with_next([=](UserPhotosSlice &&slice) {
|
||||
state->userSlice = std::move(slice);
|
||||
push();
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
moveRequests->events(
|
||||
) | rpl::filter([=] {
|
||||
return (state->current.count > 1);
|
||||
}) | rpl::start_with_next([=](int shift) {
|
||||
state->current.index = std::clamp(
|
||||
((state->current.index + shift + state->current.count)
|
||||
% state->current.count),
|
||||
0,
|
||||
state->current.count - 1);
|
||||
push(true);
|
||||
}, lifetime);
|
||||
|
||||
peer->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return state->waitingLoad
|
||||
&& (state->photoView
|
||||
? (!!state->photoView->image(Data::PhotoSize::Large))
|
||||
: (state->userpicView && state->userpicView->image()));
|
||||
}) | rpl::start_with_next([=] {
|
||||
push();
|
||||
}, lifetime);
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
return { .value = std::move(value), .move = std::move(move) };
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> open,
|
||||
Fn<bool()> videoPaused) {
|
||||
const auto type = peer->isUser()
|
||||
? PeerShortInfoType::User
|
||||
: peer->isBroadcast()
|
||||
? PeerShortInfoType::Channel
|
||||
: PeerShortInfoType::Group;
|
||||
auto userpic = UserpicValue(peer);
|
||||
auto result = Box<PeerShortInfoBox>(
|
||||
type,
|
||||
FieldsValue(peer),
|
||||
StatusValue(peer),
|
||||
std::move(userpic.value),
|
||||
std::move(videoPaused));
|
||||
|
||||
result->openRequests(
|
||||
) | rpl::start_with_next(open, result->lifetime());
|
||||
|
||||
result->moveRequests(
|
||||
) | rpl::start_with_next(userpic.move, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation) {
|
||||
const auto open = [=] { navigation->showPeerHistory(peer); };
|
||||
const auto videoIsPaused = [=] {
|
||||
return navigation->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
};
|
||||
return PrepareShortInfoBox(
|
||||
peer,
|
||||
open,
|
||||
videoIsPaused);
|
||||
}
|
||||
|
||||
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
|
||||
return StatusValue(peer);
|
||||
}
|
||||
|
||||
PreparedShortInfoUserpic PrepareShortInfoUserpic(not_null<PeerData*> peer) {
|
||||
return UserpicValue(peer);
|
||||
}
|
||||
42
Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h
Normal file
42
Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
struct PeerShortInfoUserpic;
|
||||
|
||||
struct PreparedShortInfoUserpic {
|
||||
rpl::producer<PeerShortInfoUserpic> value;
|
||||
Fn<void(int)> move;
|
||||
};
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> open,
|
||||
Fn<bool()> videoPaused);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation);
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> PrepareShortInfoStatus(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] PreparedShortInfoUserpic PrepareShortInfoUserpic(
|
||||
not_null<PeerData*> peer);
|
||||
68
Telegram/SourceFiles/boxes/phone_banned_box.cpp
Normal file
68
Telegram/SourceFiles/boxes/phone_banned_box.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/phone_banned_box.h"
|
||||
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "core/click_handler_types.h" // UrlClickHandler
|
||||
#include "base/qthelp_url.h" // qthelp::url_encode
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
namespace {
|
||||
|
||||
void SendToBannedHelp(const QString &phone) {
|
||||
const auto version = QString::fromLatin1(AppVersionStr)
|
||||
+ (cAlphaVersion()
|
||||
? qsl(" alpha %1").arg(cAlphaVersion())
|
||||
: (AppBetaVersion ? " beta" : ""));
|
||||
|
||||
const auto subject = qsl("Banned phone number: ") + phone;
|
||||
|
||||
const auto body = qsl("\
|
||||
I'm trying to use my mobile phone number: ") + phone + qsl("\n\
|
||||
But Telegram says it's banned. Please help.\n\
|
||||
\n\
|
||||
App version: ") + version + qsl("\n\
|
||||
OS version: ") + ::Platform::SystemVersionPretty() + qsl("\n\
|
||||
Locale: ") + ::Platform::SystemLanguage();
|
||||
|
||||
const auto url = "mailto:?to="
|
||||
+ qthelp::url_encode("login@stel.com")
|
||||
+ "&subject="
|
||||
+ qthelp::url_encode(subject)
|
||||
+ "&body="
|
||||
+ qthelp::url_encode(body);
|
||||
|
||||
UrlClickHandler::Open(url);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShowPhoneBannedError(
|
||||
not_null<Window::Controller*> controller,
|
||||
const QString &phone) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto close = [=] {
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
};
|
||||
*box = controller->show(
|
||||
Box<Ui::ConfirmBox>(
|
||||
tr::lng_signin_banned_text(tr::now),
|
||||
tr::lng_box_ok(tr::now),
|
||||
tr::lng_signin_banned_help(tr::now),
|
||||
close,
|
||||
[=] { SendToBannedHelp(phone); close(); }),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
20
Telegram/SourceFiles/boxes/phone_banned_box.h
Normal file
20
Telegram/SourceFiles/boxes/phone_banned_box.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ShowPhoneBannedError(
|
||||
not_null<Window::Controller*> controller,
|
||||
const QString &phone);
|
||||
|
||||
} // namespace Ui
|
||||
129
Telegram/SourceFiles/boxes/pin_messages_box.cpp
Normal file
129
Telegram/SourceFiles/boxes/pin_messages_box.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/pin_messages_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool IsOldForPin(MsgId id, not_null<PeerData*> peer) {
|
||||
const auto normal = peer->migrateToOrMe();
|
||||
const auto migrated = normal->migrateFrom();
|
||||
const auto top = Data::ResolveTopPinnedId(normal, migrated);
|
||||
if (!top) {
|
||||
return false;
|
||||
} else if (peer == migrated) {
|
||||
return top.channel || (id < top.msg);
|
||||
} else if (migrated) {
|
||||
return top.channel && (id < top.msg);
|
||||
} else {
|
||||
return (id < top.msg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PinMessageBox::PinMessageBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().mtp())
|
||||
, _msgId(msgId)
|
||||
, _pinningOld(IsOldForPin(msgId, peer))
|
||||
, _text(
|
||||
this,
|
||||
(_pinningOld
|
||||
? tr::lng_pinned_pin_old_sure(tr::now)
|
||||
: (peer->isChat() || peer->isMegagroup())
|
||||
? tr::lng_pinned_pin_sure_group(tr::now)
|
||||
: tr::lng_pinned_pin_sure(tr::now)),
|
||||
st::boxLabel) {
|
||||
}
|
||||
|
||||
void PinMessageBox::prepare() {
|
||||
addButton(tr::lng_pinned_pin(), [this] { pinMessage(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
|
||||
if (_peer->isUser() && !_peer->isSelf()) {
|
||||
_pinForPeer.create(
|
||||
this,
|
||||
tr::lng_pinned_also_for_other(
|
||||
tr::now,
|
||||
lt_user,
|
||||
_peer->shortName()),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
_checkbox = _pinForPeer;
|
||||
} else if (!_pinningOld && (_peer->isChat() || _peer->isMegagroup())) {
|
||||
_notify.create(
|
||||
this,
|
||||
tr::lng_pinned_notify(tr::now),
|
||||
true,
|
||||
st::defaultBoxCheckbox);
|
||||
_checkbox = _notify;
|
||||
}
|
||||
|
||||
auto height = st::boxPadding.top()
|
||||
+ _text->height()
|
||||
+ st::boxPadding.bottom();
|
||||
if (_checkbox) {
|
||||
height += st::boxMediumSkip + _checkbox->heightNoMargins();
|
||||
}
|
||||
setDimensions(st::boxWidth, height);
|
||||
}
|
||||
|
||||
void PinMessageBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
_text->moveToLeft(st::boxPadding.left(), st::boxPadding.top());
|
||||
if (_checkbox) {
|
||||
_checkbox->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
_text->y() + _text->height() + st::boxMediumSkip);
|
||||
}
|
||||
}
|
||||
|
||||
void PinMessageBox::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
pinMessage();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void PinMessageBox::pinMessage() {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = MTPmessages_UpdatePinnedMessage::Flags(0);
|
||||
if (_notify && !_notify->checked()) {
|
||||
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_silent;
|
||||
}
|
||||
if (_pinForPeer && !_pinForPeer->checked()) {
|
||||
flags |= MTPmessages_UpdatePinnedMessage::Flag::f_pm_oneside;
|
||||
}
|
||||
_requestId = _api.request(MTPmessages_UpdatePinnedMessage(
|
||||
MTP_flags(flags),
|
||||
_peer->input,
|
||||
MTP_int(_msgId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
Ui::hideLayer();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::hideLayer();
|
||||
}).send();
|
||||
}
|
||||
43
Telegram/SourceFiles/boxes/pin_messages_box.h
Normal file
43
Telegram/SourceFiles/boxes/pin_messages_box.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Ui {
|
||||
class Checkbox;
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
|
||||
class PinMessageBox final : public Ui::BoxContent {
|
||||
public:
|
||||
PinMessageBox(QWidget*, not_null<PeerData*> peer, MsgId msgId);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
void pinMessage();
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
MsgId _msgId = 0;
|
||||
bool _pinningOld = false;
|
||||
|
||||
object_ptr<Ui::FlatLabel> _text;
|
||||
object_ptr<Ui::Checkbox> _notify = { nullptr };
|
||||
object_ptr<Ui::Checkbox> _pinForPeer = { nullptr };
|
||||
QPointer<Ui::Checkbox> _checkbox;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/rate_call_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "mainwindow.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxRating = 5;
|
||||
constexpr auto kRateCallCommentLengthMax = 200;
|
||||
|
||||
} // namespace
|
||||
|
||||
RateCallBox::RateCallBox(
|
||||
QWidget*,
|
||||
not_null<Main::Session*> session,
|
||||
uint64 callId,
|
||||
uint64 callAccessHash)
|
||||
: _session(session)
|
||||
, _api(&_session->mtp())
|
||||
, _callId(callId)
|
||||
, _callAccessHash(callAccessHash) {
|
||||
}
|
||||
|
||||
void RateCallBox::prepare() {
|
||||
setTitle(tr::lng_call_rate_label());
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
|
||||
for (auto i = 0; i < kMaxRating; ++i) {
|
||||
_stars.emplace_back(this, st::callRatingStar);
|
||||
_stars.back()->setClickedCallback([this, value = i + 1] { ratingChanged(value); });
|
||||
_stars.back()->show();
|
||||
}
|
||||
|
||||
updateMaxHeight();
|
||||
}
|
||||
|
||||
void RateCallBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
auto starsWidth = (_stars.size() * st::callRatingStar.width);
|
||||
auto starLeft = (width() - starsWidth) / 2;
|
||||
auto starTop = st::callRatingStarTop;
|
||||
for (auto &star : _stars) {
|
||||
star->moveToLeft(starLeft, starTop);
|
||||
starLeft += star->width();
|
||||
}
|
||||
if (_comment) {
|
||||
_comment->moveToLeft(st::callRatingPadding.left(), _stars.back()->bottomNoMargins() + st::callRatingCommentTop);
|
||||
}
|
||||
}
|
||||
|
||||
void RateCallBox::ratingChanged(int value) {
|
||||
Expects(value > 0 && value <= kMaxRating);
|
||||
if (!_rating) {
|
||||
clearButtons();
|
||||
addButton(tr::lng_send_button(), [this] { send(); });
|
||||
addButton(tr::lng_cancel(), [this] { closeBox(); });
|
||||
}
|
||||
_rating = value;
|
||||
|
||||
for (auto i = 0; i < kMaxRating; ++i) {
|
||||
_stars[i]->setIconOverride((i < value) ? &st::callRatingStarFilled : nullptr);
|
||||
_stars[i]->setRippleColorOverride((i < value) ? &st::lightButtonBgOver : nullptr);
|
||||
}
|
||||
if (value < kMaxRating) {
|
||||
if (!_comment) {
|
||||
_comment.create(
|
||||
this,
|
||||
st::callRatingComment,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_call_rate_comment());
|
||||
_comment->show();
|
||||
_comment->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
_comment->setMaxLength(kRateCallCommentLengthMax);
|
||||
_comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height());
|
||||
|
||||
updateMaxHeight();
|
||||
connect(_comment, &Ui::InputField::resized, [=] { commentResized(); });
|
||||
connect(_comment, &Ui::InputField::submitted, [=] { send(); });
|
||||
connect(_comment, &Ui::InputField::cancelled, [=] { closeBox(); });
|
||||
}
|
||||
_comment->setFocusFast();
|
||||
} else if (_comment) {
|
||||
_comment.destroy();
|
||||
updateMaxHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void RateCallBox::setInnerFocus() {
|
||||
if (_comment) {
|
||||
_comment->setFocusFast();
|
||||
} else {
|
||||
setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void RateCallBox::commentResized() {
|
||||
updateMaxHeight();
|
||||
update();
|
||||
}
|
||||
|
||||
void RateCallBox::send() {
|
||||
Expects(_rating > 0 && _rating <= kMaxRating);
|
||||
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
auto comment = _comment ? _comment->getLastText().trimmed() : QString();
|
||||
_requestId = _api.request(MTPphone_SetCallRating(
|
||||
MTP_flags(0),
|
||||
MTP_inputPhoneCall(MTP_long(_callId), MTP_long(_callAccessHash)),
|
||||
MTP_int(_rating),
|
||||
MTP_string(comment)
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
_session->api().applyUpdates(updates);
|
||||
closeBox();
|
||||
}).fail([=](const MTP::Error &error) { closeBox(); }).send();
|
||||
}
|
||||
|
||||
void RateCallBox::updateMaxHeight() {
|
||||
auto newHeight = st::callRatingPadding.top() + st::callRatingStarTop + _stars.back()->heightNoMargins() + st::callRatingPadding.bottom();
|
||||
if (_comment) {
|
||||
newHeight += st::callRatingCommentTop + _comment->height();
|
||||
}
|
||||
setDimensions(st::boxWideWidth, newHeight);
|
||||
}
|
||||
@@ -19,7 +19,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "confirm_box.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
#include "history/history_drag_area.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
|
||||
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -81,7 +81,7 @@ private:
|
||||
Full _data;
|
||||
|
||||
object_ptr<Inner> _inner;
|
||||
QPointer<ConfirmBox> _terminateBox;
|
||||
QPointer<Ui::ConfirmBox> _terminateBox;
|
||||
|
||||
base::Timer _shortPollTimer;
|
||||
|
||||
@@ -250,7 +250,7 @@ void SessionsContent::terminate(Fn<void()> terminateRequest, QString message) {
|
||||
terminateRequest();
|
||||
});
|
||||
_terminateBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
Box<Ui::ConfirmBox>(
|
||||
message,
|
||||
tr::lng_settings_reset_button(tr::now),
|
||||
st::attentionBoxButton,
|
||||
|
||||
@@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mainwidget.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
@@ -72,7 +72,7 @@ protected:
|
||||
int visibleBottom) override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void enterEventHook(QEvent *e) override;
|
||||
void enterEventHook(QEnterEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
@@ -906,7 +906,7 @@ void ShareBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void ShareBox::Inner::enterEventHook(QEvent *e) {
|
||||
void ShareBox::Inner::enterEventHook(QEnterEvent *e) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
@@ -1113,7 +1113,7 @@ QString AppendShareGameScoreUrl(
|
||||
auto channelAccessHash = uint64(channel ? channel->access : 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.channel.bare;
|
||||
shareHashDataInts[2] = fullId.msg;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
@@ -1155,7 +1155,8 @@ void ShareGameScoreByHash(
|
||||
|
||||
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
|
||||
Ui::show(Box<InformBox>(tr::lng_confirm_phone_link_invalid(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1175,19 +1176,19 @@ void ShareGameScoreByHash(
|
||||
//// Check next 64 bits of SHA1() of data.
|
||||
//auto skipSha1Part = sizeof(channelAccessHash);
|
||||
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
|
||||
// Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// return;
|
||||
//}
|
||||
|
||||
// Check 128 bits of SHA1() of data.
|
||||
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
|
||||
if (hashDataInts[0] != session->userId().bare) {
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1195,20 +1196,19 @@ void ShareGameScoreByHash(
|
||||
auto channelAccessHash = hashDataInts[3];
|
||||
//auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash);
|
||||
//if (channelAccessHashInts[0] != hashDataInts[3]) {
|
||||
// Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// return;
|
||||
//}
|
||||
|
||||
if (((hashDataInts[1] >> 40) != 0)
|
||||
|| ((hashDataInts[2] >> 32) != 0)
|
||||
|| (!hashDataInts[1] && channelAccessHash)) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto channelId = ChannelId(hashDataInts[1]);
|
||||
auto msgId = MsgId(hashDataInts[2]);
|
||||
auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session->data().message(channelId, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
@@ -1219,7 +1219,8 @@ void ShareGameScoreByHash(
|
||||
if (const auto item = session->data().message(channel, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
Ui::show(Box<InformBox>(tr::lng_edit_deleted(tr::now)));
|
||||
Ui::show(Box<Ui::InformBox>(
|
||||
tr::lng_edit_deleted(tr::now)));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,12 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "chat_helpers/send_context_menu.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
@@ -35,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -87,6 +90,7 @@ protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
@@ -113,6 +117,8 @@ private:
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
void send(not_null<DocumentData*> sticker, Api::SendOptions options);
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
@@ -144,6 +150,8 @@ private:
|
||||
base::Timer _previewTimer;
|
||||
int _previewShown = -1;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
rpl::event_stream<uint64> _setInstalled;
|
||||
rpl::event_stream<uint64> _setArchived;
|
||||
rpl::event_stream<> _updateControls;
|
||||
@@ -257,7 +265,7 @@ void StickerSetBox::handleError(Error error) {
|
||||
switch (error) {
|
||||
case Error::NotFound:
|
||||
_controller->show(
|
||||
Box<InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
Box<Ui::InformBox>(tr::lng_stickers_not_found(tr::now)));
|
||||
break;
|
||||
default: Unexpected("Error in StickerSetBox::handleError.");
|
||||
}
|
||||
@@ -404,7 +412,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
|
||||
auto p = StickersPack();
|
||||
p.reserve(stickers.size());
|
||||
for (auto j = 0, c = stickers.size(); j != c; ++j) {
|
||||
for (auto j = 0, c = int(stickers.size()); j != c; ++j) {
|
||||
auto doc = _controller->session().data().document(stickers[j].v);
|
||||
if (!doc || !doc->sticker()) continue;
|
||||
|
||||
@@ -532,7 +540,7 @@ void StickerSetBox::Inner::installDone(
|
||||
auto &order = isMasks
|
||||
? stickers.maskSetsOrderRef()
|
||||
: stickers.setsOrderRef();
|
||||
const auto insertAtIndex = 0, currentIndex = order.indexOf(_setId);
|
||||
const auto insertAtIndex = 0, currentIndex = int(order.indexOf(_setId));
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
order.removeAt(currentIndex);
|
||||
@@ -577,10 +585,14 @@ void StickerSetBox::Inner::installDone(
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
int index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index >= 0 && index < _pack.size()) {
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
@@ -605,18 +617,62 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_previewShown = -1;
|
||||
return;
|
||||
}
|
||||
if (_previewTimer.isActive()) {
|
||||
_previewTimer.cancel();
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index >= 0 && index < _pack.size() && !isMasksSet()) {
|
||||
const auto sticker = _pack[index];
|
||||
Ui::PostponeCall(crl::guard(_controller, [=] {
|
||||
if (_controller->content()->sendExistingDocument(sticker)) {
|
||||
Ui::hideSettingsAndLayer();
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (!_previewTimer.isActive()) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size() || isMasksSet()) {
|
||||
return;
|
||||
}
|
||||
send(_pack[index], Api::SendOptions());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::send(
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options) {
|
||||
const auto controller = _controller;
|
||||
Ui::PostponeCall(controller, [=] {
|
||||
if (controller->content()->sendExistingDocument(sticker, options)) {
|
||||
Ui::hideSettingsAndLayer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
const auto type = _controller->content()->sendMenuType();
|
||||
if (type == SendMenu::Type::Disabled) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
send(document, options);
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(this, type, sendSelected));
|
||||
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
_menu->addAction(
|
||||
(document->owner().stickers().isFaved(document)
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker);
|
||||
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateSelected() {
|
||||
|
||||
@@ -11,13 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
|
||||
class ConfirmBox;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class ConfirmBox;
|
||||
class PlainShadow;
|
||||
} // namespace Ui
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
void setCounter(int counter);
|
||||
|
||||
QString _text;
|
||||
Dialogs::Layout::UnreadBadgeStyle _st;
|
||||
Dialogs::Ui::UnreadBadgeStyle _st;
|
||||
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ StickersBox::CounterWidget::CounterWidget(
|
||||
: RpWidget(parent) {
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
|
||||
_st.sizeId = Dialogs::Ui::UnreadBadgeInStickersBox;
|
||||
_st.textTop = st::stickersFeaturedBadgeTextTop;
|
||||
_st.size = st::stickersFeaturedBadgeSize;
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
@@ -323,7 +323,7 @@ void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
Painter p(&dummy);
|
||||
|
||||
auto newWidth = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
|
||||
resize(newWidth, st::stickersFeaturedBadgeSize);
|
||||
}
|
||||
@@ -334,7 +334,7 @@ void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
|
||||
if (!_text.isEmpty()) {
|
||||
auto unreadRight = rtl() ? 0 : width();
|
||||
auto unreadTop = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/special_fields.h"
|
||||
|
||||
class ConfirmBox;
|
||||
|
||||
namespace style {
|
||||
struct RippleAnimation;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class ConfirmBox;
|
||||
class PlainShadow;
|
||||
class RippleAnimation;
|
||||
class SettingsSlider;
|
||||
|
||||
@@ -7,13 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/username_box.h"
|
||||
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/special_fields.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -23,14 +21,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinUsernameLength = 5;
|
||||
|
||||
} // namespace
|
||||
|
||||
UsernameBox::UsernameBox(QWidget*, not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _textStyle(st::usernameTextStyle)
|
||||
, _font(st::boxTextFont)
|
||||
, _padding(st::usernamePadding)
|
||||
, _textCenterTop((_textStyle.lineHeight - _font->height) / 2)
|
||||
, _api(&_session->mtp())
|
||||
, _username(
|
||||
this,
|
||||
@@ -39,8 +35,8 @@ UsernameBox::UsernameBox(QWidget*, not_null<Main::Session*> session)
|
||||
session->user()->username,
|
||||
QString())
|
||||
, _link(this, QString(), st::boxLinkButton)
|
||||
, _about(st::boxWidth - st::usernamePadding.left())
|
||||
, _checkTimer(this) {
|
||||
, _about(st::boxWidth - _padding.left())
|
||||
, _checkTimer([=] { check(); }) {
|
||||
}
|
||||
|
||||
void UsernameBox::prepare() {
|
||||
@@ -57,11 +53,15 @@ void UsernameBox::prepare() {
|
||||
connect(_username, &Ui::MaskedInputField::submitted, [=] { save(); });
|
||||
_link->addClickHandler([=] { linkClick(); });
|
||||
|
||||
_about.setText(st::usernameTextStyle, tr::lng_username_about(tr::now));
|
||||
setDimensions(st::boxWidth, st::usernamePadding.top() + _username->height() + st::usernameSkip + _about.countHeight(st::boxWidth - st::usernamePadding.left()) + 3 * st::usernameTextStyle.lineHeight + st::usernamePadding.bottom());
|
||||
|
||||
_checkTimer->setSingleShot(true);
|
||||
connect(_checkTimer, &QTimer::timeout, [=] { check(); });
|
||||
_about.setText(_textStyle, tr::lng_username_about(tr::now));
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
_padding.top()
|
||||
+ _username->height()
|
||||
+ st::usernameSkip
|
||||
+ _about.countHeight(st::boxWidth - _padding.left())
|
||||
+ 3 * _textStyle.lineHeight
|
||||
+ _padding.bottom());
|
||||
|
||||
updateLinkText();
|
||||
}
|
||||
@@ -75,106 +75,178 @@ void UsernameBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
Painter p(this);
|
||||
|
||||
p.setFont(st::boxTextFont);
|
||||
const auto textTop = _username->y()
|
||||
+ _username->height()
|
||||
+ ((st::usernameSkip - _font->height) / 2);
|
||||
|
||||
p.setFont(_font);
|
||||
if (!_errorText.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText);
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
textTop,
|
||||
width(),
|
||||
_errorText);
|
||||
} else if (!_goodText.isEmpty()) {
|
||||
p.setPen(st::boxTextFgGood);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText);
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
textTop,
|
||||
width(),
|
||||
_goodText);
|
||||
} else {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username->y() + _username->height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), tr::lng_username_choose(tr::now));
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
textTop,
|
||||
width(),
|
||||
tr::lng_username_choose(tr::now));
|
||||
}
|
||||
p.setPen(st::boxTextFg);
|
||||
int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw);
|
||||
_about.drawLeft(p, st::usernamePadding.left(), _username->y() + _username->height() + st::usernameSkip, availw, width());
|
||||
const auto availW = st::boxWidth - _padding.left();
|
||||
const auto h = _about.countHeight(availW);
|
||||
_about.drawLeft(
|
||||
p,
|
||||
_padding.left(),
|
||||
_username->y() + _username->height() + st::usernameSkip,
|
||||
availW,
|
||||
width());
|
||||
|
||||
int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2);
|
||||
const auto linkTop = _username->y()
|
||||
+ _username->height()
|
||||
+ st::usernameSkip
|
||||
+ h
|
||||
+ _textStyle.lineHeight
|
||||
+ _textCenterTop;
|
||||
if (_link->isHidden()) {
|
||||
p.drawTextLeft(st::usernamePadding.left(), linky, width(), tr::lng_username_link_willbe(tr::now));
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
linkTop,
|
||||
width(),
|
||||
tr::lng_username_link_willbe(tr::now));
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
const auto link = _session->createInternalLinkFull(qsl("username"));
|
||||
p.drawTextLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2), width(), link);
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
linkTop
|
||||
+ _textStyle.lineHeight
|
||||
+ _textCenterTop,
|
||||
width(),
|
||||
link);
|
||||
} else {
|
||||
p.drawTextLeft(st::usernamePadding.left(), linky, width(), tr::lng_username_link(tr::now));
|
||||
p.drawTextLeft(
|
||||
_padding.left(),
|
||||
linkTop,
|
||||
width(),
|
||||
tr::lng_username_link(tr::now));
|
||||
}
|
||||
}
|
||||
|
||||
void UsernameBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_username->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _username->height());
|
||||
_username->moveToLeft(st::usernamePadding.left(), st::usernamePadding.top());
|
||||
_username->resize(
|
||||
width() - _padding.left() - _padding.right(),
|
||||
_username->height());
|
||||
_username->moveToLeft(_padding.left(), _padding.top());
|
||||
|
||||
int32 availw = st::boxWidth - st::usernamePadding.left(), h = _about.countHeight(availw);
|
||||
int32 linky = _username->y() + _username->height() + st::usernameSkip + h + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2);
|
||||
_link->moveToLeft(st::usernamePadding.left(), linky + st::usernameTextStyle.lineHeight + ((st::usernameTextStyle.lineHeight - st::boxTextFont->height) / 2));
|
||||
const auto availW = st::boxWidth - _padding.left();
|
||||
const auto h = _about.countHeight(availW);
|
||||
const auto linkTop = _username->y()
|
||||
+ _username->height()
|
||||
+ st::usernameSkip
|
||||
+ h
|
||||
+ _textStyle.lineHeight
|
||||
+ _textCenterTop;
|
||||
_link->moveToLeft(
|
||||
_padding.left(),
|
||||
linkTop + _textStyle.lineHeight + _textCenterTop);
|
||||
}
|
||||
|
||||
void UsernameBox::save() {
|
||||
if (_saveRequestId) return;
|
||||
if (_saveRequestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sentUsername = getName();
|
||||
_saveRequestId = _api.request(MTPaccount_UpdateUsername(
|
||||
MTP_string(_sentUsername)
|
||||
)).done([=](const MTPUser &result) {
|
||||
updateDone(result);
|
||||
_saveRequestId = 0;
|
||||
_session->data().processUser(result);
|
||||
closeBox();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
updateFail(error);
|
||||
_saveRequestId = 0;
|
||||
updateFail(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void UsernameBox::check() {
|
||||
_api.request(base::take(_checkRequestId)).cancel();
|
||||
|
||||
QString name = getName();
|
||||
if (name.size() >= kMinUsernameLength) {
|
||||
_checkUsername = name;
|
||||
_checkRequestId = _api.request(MTPaccount_CheckUsername(
|
||||
MTP_string(name)
|
||||
)).done([=](const MTPBool &result) {
|
||||
checkDone(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
checkFail(error);
|
||||
}).send();
|
||||
const auto name = getName();
|
||||
if (name.size() < Ui::EditPeer::kMinUsernameLength) {
|
||||
return;
|
||||
}
|
||||
_checkUsername = name;
|
||||
_checkRequestId = _api.request(MTPaccount_CheckUsername(
|
||||
MTP_string(name)
|
||||
)).done([=](const MTPBool &result) {
|
||||
_checkRequestId = 0;
|
||||
|
||||
_errorText = (mtpIsTrue(result)
|
||||
|| _checkUsername == _session->user()->username)
|
||||
? QString()
|
||||
: tr::lng_username_occupied(tr::now);
|
||||
_goodText = _errorText.isEmpty()
|
||||
? tr::lng_username_available(tr::now)
|
||||
: QString();
|
||||
|
||||
update();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_checkRequestId = 0;
|
||||
checkFail(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void UsernameBox::changed() {
|
||||
updateLinkText();
|
||||
QString name = getName();
|
||||
const auto name = getName();
|
||||
if (name.isEmpty()) {
|
||||
if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
|
||||
_errorText = _goodText = QString();
|
||||
update();
|
||||
}
|
||||
_checkTimer->stop();
|
||||
_checkTimer.cancel();
|
||||
} else {
|
||||
int32 len = name.size();
|
||||
for (int32 i = 0; i < len; ++i) {
|
||||
QChar ch = name.at(i);
|
||||
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) {
|
||||
const auto len = int(name.size());
|
||||
for (auto i = 0; i < len; ++i) {
|
||||
const auto ch = name.at(i);
|
||||
if ((ch < 'A' || ch > 'Z')
|
||||
&& (ch < 'a' || ch > 'z')
|
||||
&& (ch < '0' || ch > '9')
|
||||
&& ch != '_'
|
||||
&& (ch != '@' || i > 0)) {
|
||||
if (_errorText != tr::lng_username_bad_symbols(tr::now)) {
|
||||
_errorText = tr::lng_username_bad_symbols(tr::now);
|
||||
update();
|
||||
}
|
||||
_checkTimer->stop();
|
||||
_checkTimer.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (name.size() < kMinUsernameLength) {
|
||||
if (name.size() < Ui::EditPeer::kMinUsernameLength) {
|
||||
if (_errorText != tr::lng_username_too_short(tr::now)) {
|
||||
_errorText = tr::lng_username_too_short(tr::now);
|
||||
update();
|
||||
}
|
||||
_checkTimer->stop();
|
||||
_checkTimer.cancel();
|
||||
} else {
|
||||
if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
|
||||
_errorText = _goodText = QString();
|
||||
update();
|
||||
}
|
||||
_checkTimer->start(UsernameCheckTimeout);
|
||||
_checkTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,28 +257,23 @@ void UsernameBox::linkClick() {
|
||||
Ui::Toast::Show(tr::lng_username_copied(tr::now));
|
||||
}
|
||||
|
||||
void UsernameBox::updateDone(const MTPUser &user) {
|
||||
_session->data().processUser(user);
|
||||
closeBox();
|
||||
}
|
||||
|
||||
void UsernameBox::updateFail(const MTP::Error &error) {
|
||||
_saveRequestId = 0;
|
||||
void UsernameBox::updateFail(const QString &error) {
|
||||
const auto self = _session->user();
|
||||
const auto &err = error.type();
|
||||
if (err == qstr("USERNAME_NOT_MODIFIED") || _sentUsername == self->username) {
|
||||
if ((error == qstr("USERNAME_NOT_MODIFIED"))
|
||||
|| (_sentUsername == self->username)) {
|
||||
self->setName(
|
||||
TextUtilities::SingleLine(self->firstName),
|
||||
TextUtilities::SingleLine(self->lastName),
|
||||
TextUtilities::SingleLine(self->nameOrPhone),
|
||||
TextUtilities::SingleLine(_sentUsername));
|
||||
closeBox();
|
||||
} else if (err == qstr("USERNAME_INVALID")) {
|
||||
} else if (error == qstr("USERNAME_INVALID")) {
|
||||
_username->setFocus();
|
||||
_username->showError();
|
||||
_errorText = tr::lng_username_invalid(tr::now);
|
||||
update();
|
||||
} else if (err == qstr("USERNAME_OCCUPIED") || err == qstr("USERNAMES_UNAVAILABLE")) {
|
||||
} else if ((error == qstr("USERNAME_OCCUPIED"))
|
||||
|| (error == qstr("USERNAMES_UNAVAILABLE"))) {
|
||||
_username->setFocus();
|
||||
_username->showError();
|
||||
_errorText = tr::lng_username_occupied(tr::now);
|
||||
@@ -216,29 +283,12 @@ void UsernameBox::updateFail(const MTP::Error &error) {
|
||||
}
|
||||
}
|
||||
|
||||
void UsernameBox::checkDone(const MTPBool &result) {
|
||||
_checkRequestId = 0;
|
||||
const auto newError = (mtpIsTrue(result)
|
||||
|| _checkUsername == _session->user()->username)
|
||||
? QString()
|
||||
: tr::lng_username_occupied(tr::now);
|
||||
const auto newGood = newError.isEmpty()
|
||||
? tr::lng_username_available(tr::now)
|
||||
: QString();
|
||||
if (_errorText != newError || _goodText != newGood) {
|
||||
_errorText = newError;
|
||||
_goodText = newGood;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UsernameBox::checkFail(const MTP::Error &error) {
|
||||
_checkRequestId = 0;
|
||||
QString err(error.type());
|
||||
if (err == qstr("USERNAME_INVALID")) {
|
||||
void UsernameBox::checkFail(const QString &error) {
|
||||
if (error == qstr("USERNAME_INVALID")) {
|
||||
_errorText = tr::lng_username_invalid(tr::now);
|
||||
update();
|
||||
} else if (err == qstr("USERNAME_OCCUPIED") && _checkUsername != _session->user()->username) {
|
||||
} else if ((error == qstr("USERNAME_OCCUPIED"))
|
||||
&& (_checkUsername != _session->user()->username)) {
|
||||
_errorText = tr::lng_username_occupied(tr::now);
|
||||
update();
|
||||
} else {
|
||||
@@ -252,8 +302,10 @@ QString UsernameBox::getName() const {
|
||||
}
|
||||
|
||||
void UsernameBox::updateLinkText() {
|
||||
QString uname = getName();
|
||||
_link->setText(st::boxTextFont->elided(_session->createInternalLinkFull(uname), st::boxWidth - st::usernamePadding.left() - st::usernamePadding.right()));
|
||||
const auto uname = getName();
|
||||
_link->setText(_font->elided(
|
||||
_session->createInternalLinkFull(uname),
|
||||
st::boxWidth - _padding.left() - _padding.right()));
|
||||
if (uname.isEmpty()) {
|
||||
if (!_link->isHidden()) {
|
||||
_link->hide();
|
||||
@@ -265,4 +317,4 @@ void UsernameBox::updateLinkText() {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Ui {
|
||||
@@ -31,11 +32,9 @@ protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateDone(const MTPUser &result);
|
||||
void updateFail(const MTP::Error &error);
|
||||
void updateFail(const QString &error);
|
||||
|
||||
void checkDone(const MTPBool &result);
|
||||
void checkFail(const MTP::Error &error);
|
||||
void checkFail(const QString &error);
|
||||
|
||||
void save();
|
||||
|
||||
@@ -48,6 +47,10 @@ private:
|
||||
void updateLinkText();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const style::TextStyle &_textStyle;
|
||||
const style::font &_font;
|
||||
const style::margins &_padding;
|
||||
const int _textCenterTop;
|
||||
MTP::Sender _api;
|
||||
|
||||
object_ptr<Ui::UsernameInput> _username;
|
||||
@@ -58,6 +61,6 @@ private:
|
||||
QString _sentUsername, _checkUsername, _errorText, _goodText;
|
||||
|
||||
Ui::Text::String _about;
|
||||
object_ptr<QTimer> _checkTimer;
|
||||
base::Timer _checkTimer;
|
||||
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ using "ui/basic.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/layers/layers.style";
|
||||
using "ui/chat/chat.style"; // GroupCallUserpics
|
||||
using "info/info.style"; // ShortInfoCover
|
||||
using "window/window.style";
|
||||
|
||||
CallSignalBars {
|
||||
@@ -542,6 +543,32 @@ groupCallPopupVolumeMenu: Menu(groupCallMenu) {
|
||||
widthMin: 210px;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
groupCallMenuCoverSize: 240px;
|
||||
groupCallPopupCoverMenu: Menu(groupCallMenu) {
|
||||
widthMin: groupCallMenuCoverSize;
|
||||
widthMax: groupCallMenuCoverSize;
|
||||
itemBgOver: groupCallMenuBg;
|
||||
}
|
||||
groupCallPopupMenuWithCover: PopupMenu(groupCallPopupMenu) {
|
||||
scrollPadding: margins(0px, 0px, 0px, 8px);
|
||||
menu: Menu(groupCallMenu) {
|
||||
widthMin: groupCallMenuCoverSize;
|
||||
widthMax: groupCallMenuCoverSize;
|
||||
}
|
||||
}
|
||||
groupCallMenuCover: ShortInfoCover(shortInfoCover) {
|
||||
size: groupCallMenuCoverSize;
|
||||
namePosition: point(17px, 28px);
|
||||
statusPosition: point(17px, 8px);
|
||||
}
|
||||
groupCallMenuAbout: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
linkFg: groupCallActiveFg;
|
||||
}
|
||||
minWidth: 200px;
|
||||
maxHeight: 92px;
|
||||
}
|
||||
|
||||
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
|
||||
groupCallRecordingTimerFont: font(12px);
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_user.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -102,23 +102,25 @@ public:
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) override;
|
||||
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
||||
void stopLastActionRipple() override;
|
||||
void rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) override;
|
||||
void rightActionStopLastRipple() override;
|
||||
|
||||
int nameIconWidth() const override {
|
||||
return 0;
|
||||
}
|
||||
QSize actionSize() const override {
|
||||
QSize rightActionSize() const override {
|
||||
return peer()->isUser() ? QSize(_st->width, _st->height) : QSize();
|
||||
}
|
||||
QMargins actionMargins() const override {
|
||||
QMargins rightActionMargins() const override {
|
||||
return QMargins(
|
||||
0,
|
||||
0,
|
||||
st::defaultPeerListItem.photoPosition.x(),
|
||||
0);
|
||||
}
|
||||
void paintAction(
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
@@ -141,7 +143,7 @@ private:
|
||||
};
|
||||
|
||||
BoxController::Row::Row(not_null<HistoryItem*> item)
|
||||
: PeerListRow(item->history()->peer, item->id)
|
||||
: PeerListRow(item->history()->peer, item->id.bare)
|
||||
, _items(1, item)
|
||||
, _date(ItemDateTime(item).date())
|
||||
, _type(ComputeType(item))
|
||||
@@ -168,14 +170,14 @@ void BoxController::Row::paintStatusText(Painter &p, const style::PeerListItem &
|
||||
PeerListRow::paintStatusText(p, st, x, y, availableWidth, outerWidth, selected);
|
||||
}
|
||||
|
||||
void BoxController::Row::paintAction(
|
||||
void BoxController::Row::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
auto size = actionSize();
|
||||
auto size = rightActionSize();
|
||||
if (_actionRipple) {
|
||||
_actionRipple->paint(
|
||||
p,
|
||||
@@ -243,7 +245,7 @@ BoxController::Row::CallType BoxController::Row::ComputeCallType(
|
||||
return CallType::Voice;
|
||||
}
|
||||
|
||||
void BoxController::Row::addActionRipple(QPoint point, Fn<void()> updateCallback) {
|
||||
void BoxController::Row::rightActionAddRipple(QPoint point, Fn<void()> updateCallback) {
|
||||
if (!_actionRipple) {
|
||||
auto mask = Ui::RippleAnimation::ellipseMask(
|
||||
QSize(_st->rippleAreaSize, _st->rippleAreaSize));
|
||||
@@ -255,7 +257,7 @@ void BoxController::Row::addActionRipple(QPoint point, Fn<void()> updateCallback
|
||||
_actionRipple->add(point - _st->rippleAreaPosition);
|
||||
}
|
||||
|
||||
void BoxController::Row::stopLastActionRipple() {
|
||||
void BoxController::Row::rightActionStopLastRipple() {
|
||||
if (_actionRipple) {
|
||||
_actionRipple->lastStop();
|
||||
}
|
||||
@@ -379,7 +381,7 @@ void BoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
});
|
||||
}
|
||||
|
||||
void BoxController::rowActionClicked(not_null<PeerListRow*> row) {
|
||||
void BoxController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
auto user = row->peer()->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowActionClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
|
||||
@@ -12,8 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_app_config.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/rate_call_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/rate_call_box.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
@@ -47,6 +47,7 @@ namespace {
|
||||
constexpr auto kMinLayer = 65;
|
||||
constexpr auto kHangupTimeoutMs = 5000;
|
||||
constexpr auto kSha256Size = 32;
|
||||
constexpr auto kAuthKeySize = 256;
|
||||
const auto kDefaultVersion = "2.4.4"_q;
|
||||
|
||||
const auto RegisterTag = tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
@@ -133,12 +134,11 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
|
||||
|
||||
[[nodiscard]] QVector<MTPstring> WrapVersions(
|
||||
const std::vector<std::string> &data) {
|
||||
auto result = QVector<MTPstring>();
|
||||
result.reserve(data.size());
|
||||
for (const auto &version : data) {
|
||||
result.push_back(MTP_string(version));
|
||||
}
|
||||
return result;
|
||||
return ranges::views::all(
|
||||
data
|
||||
) | ranges::views::transform([=](const std::string &string) {
|
||||
return MTP_string(string);
|
||||
}) | ranges::to<QVector<MTPstring>>;
|
||||
}
|
||||
|
||||
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
|
||||
@@ -172,6 +172,8 @@ Call::Call(
|
||||
if (_type == Type::Outgoing) {
|
||||
setState(State::Requesting);
|
||||
} else {
|
||||
const auto &config = _user->session().serverConfig();
|
||||
_discardByTimeoutTimer.callOnce(config.callRingTimeoutMs);
|
||||
startWaitingTrack();
|
||||
}
|
||||
setupOutgoingVideo();
|
||||
@@ -247,16 +249,17 @@ void Call::startOutgoing() {
|
||||
|
||||
setState(State::Waiting);
|
||||
|
||||
auto &call = result.c_phone_phoneCall();
|
||||
const auto &call = result.c_phone_phoneCall();
|
||||
_user->session().data().processUsers(call.vusers());
|
||||
if (call.vphone_call().type() != mtpc_phoneCallWaiting) {
|
||||
LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()"));
|
||||
LOG(("Call Error: Expected phoneCallWaiting in response to "
|
||||
"phone.requestCall()"));
|
||||
finish(FinishType::Failed);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &phoneCall = call.vphone_call();
|
||||
auto &waitingCall = phoneCall.c_phoneCallWaiting();
|
||||
const auto &phoneCall = call.vphone_call();
|
||||
const auto &waitingCall = phoneCall.c_phoneCallWaiting();
|
||||
_id = waitingCall.vid().v;
|
||||
_accessHash = waitingCall.vaccess_hash().v;
|
||||
if (_finishAfterRequestingCall != FinishType::None) {
|
||||
@@ -272,7 +275,7 @@ void Call::startOutgoing() {
|
||||
_discardByTimeoutTimer.callOnce(config.callReceiveTimeoutMs);
|
||||
handleUpdate(phoneCall);
|
||||
}).fail([this](const MTP::Error &error) {
|
||||
handleRequestError(error);
|
||||
handleRequestError(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -287,7 +290,7 @@ void Call::startIncoming() {
|
||||
setState(State::WaitingIncoming);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
handleRequestError(error);
|
||||
handleRequestError(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -327,7 +330,7 @@ void Call::actuallyAnswer() {
|
||||
)).done([=](const MTPphone_PhoneCall &result) {
|
||||
Expects(result.type() == mtpc_phone_phoneCall);
|
||||
|
||||
auto &call = result.c_phone_phoneCall();
|
||||
const auto &call = result.c_phone_phoneCall();
|
||||
_user->session().data().processUsers(call.vusers());
|
||||
if (call.vphone_call().type() != mtpc_phoneCallWaiting) {
|
||||
LOG(("Call Error: "
|
||||
@@ -338,7 +341,7 @@ void Call::actuallyAnswer() {
|
||||
|
||||
handleUpdate(call.vphone_call());
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
handleRequestError(error);
|
||||
handleRequestError(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -410,10 +413,14 @@ void Call::hangup() {
|
||||
if (state == State::Busy) {
|
||||
_delegate->callFinished(this);
|
||||
} else {
|
||||
auto missed = (state == State::Ringing || (state == State::Waiting && _type == Type::Outgoing));
|
||||
auto declined = isIncomingWaiting();
|
||||
auto reason = missed ? MTP_phoneCallDiscardReasonMissed() :
|
||||
declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup();
|
||||
const auto missed = (state == State::Ringing
|
||||
|| (state == State::Waiting && _type == Type::Outgoing));
|
||||
const auto declined = isIncomingWaiting();
|
||||
const auto reason = missed
|
||||
? MTP_phoneCallDiscardReasonMissed()
|
||||
: declined
|
||||
? MTP_phoneCallDiscardReasonBusy()
|
||||
: MTP_phoneCallDiscardReasonHangup();
|
||||
finish(FinishType::Ended, reason);
|
||||
}
|
||||
}
|
||||
@@ -438,7 +445,7 @@ QString Call::getDebugLog() const {
|
||||
|
||||
void Call::startWaitingTrack() {
|
||||
_waitingTrack = Media::Audio::Current().createTrack();
|
||||
auto trackFileName = Core::App().settings().getSoundPath(
|
||||
const auto trackFileName = Core::App().settings().getSoundPath(
|
||||
(_type == Type::Outgoing)
|
||||
? qsl("call_outgoing")
|
||||
: qsl("call_incoming"));
|
||||
@@ -458,13 +465,13 @@ void Call::sendSignalingData(const QByteArray &data) {
|
||||
finish(FinishType::Failed);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
handleRequestError(error);
|
||||
handleRequestError(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
float64 Call::getWaitingSoundPeakValue() const {
|
||||
if (_waitingTrack) {
|
||||
auto when = crl::now() + kSoundSampleMs / 4;
|
||||
const auto when = crl::now() + kSoundSampleMs / 4;
|
||||
return _waitingTrack->getPeakValue(when);
|
||||
}
|
||||
return 0.;
|
||||
@@ -478,20 +485,29 @@ bytes::vector Call::getKeyShaForFingerprint() const {
|
||||
Expects(isKeyShaForFingerprintReady());
|
||||
Expects(!_ga.empty());
|
||||
|
||||
auto encryptedChatAuthKey = bytes::vector(_authKey.size() + _ga.size(), gsl::byte {});
|
||||
bytes::copy(gsl::make_span(encryptedChatAuthKey).subspan(0, _authKey.size()), _authKey);
|
||||
bytes::copy(gsl::make_span(encryptedChatAuthKey).subspan(_authKey.size(), _ga.size()), _ga);
|
||||
auto encryptedChatAuthKey = bytes::vector(
|
||||
_authKey.size() + _ga.size(),
|
||||
gsl::byte{});
|
||||
bytes::copy(
|
||||
gsl::make_span(encryptedChatAuthKey).subspan(0, _authKey.size()),
|
||||
_authKey);
|
||||
bytes::copy(
|
||||
gsl::make_span(encryptedChatAuthKey).subspan(
|
||||
_authKey.size(),
|
||||
_ga.size()),
|
||||
_ga);
|
||||
return openssl::Sha256(encryptedChatAuthKey);
|
||||
}
|
||||
|
||||
bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
switch (call.type()) {
|
||||
case mtpc_phoneCallRequested: {
|
||||
auto &data = call.c_phoneCallRequested();
|
||||
const auto &data = call.c_phoneCallRequested();
|
||||
if (_type != Type::Incoming
|
||||
|| _id != 0
|
||||
|| peerToUser(_user->id) != UserId(data.vadmin_id())) {
|
||||
Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
|
||||
Unexpected("phoneCallRequested call inside an existing call "
|
||||
"handleUpdate()");
|
||||
}
|
||||
if (_user->session().userId() != UserId(data.vparticipant_id())) {
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2."
|
||||
@@ -503,7 +519,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
|
||||
_id = data.vid().v;
|
||||
_accessHash = data.vaccess_hash().v;
|
||||
auto gaHashBytes = bytes::make_span(data.vg_a_hash().v);
|
||||
const auto gaHashBytes = bytes::make_span(data.vg_a_hash().v);
|
||||
if (gaHashBytes.size() != kSha256Size) {
|
||||
LOG(("Call Error: Wrong g_a_hash size %1, expected %2."
|
||||
).arg(gaHashBytes.size()
|
||||
@@ -515,7 +531,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCallEmpty: {
|
||||
auto &data = call.c_phoneCallEmpty();
|
||||
const auto &data = call.c_phoneCallEmpty();
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
@@ -524,7 +540,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCallWaiting: {
|
||||
auto &data = call.c_phoneCallWaiting();
|
||||
const auto &data = call.c_phoneCallWaiting();
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
@@ -539,7 +555,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCall: {
|
||||
auto &data = call.c_phoneCall();
|
||||
const auto &data = call.c_phoneCall();
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
@@ -551,12 +567,12 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCallDiscarded: {
|
||||
auto &data = call.c_phoneCallDiscarded();
|
||||
const auto &data = call.c_phoneCallDiscarded();
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
if (data.is_need_debug()) {
|
||||
auto debugLog = _instance
|
||||
const auto debugLog = _instance
|
||||
? _instance->getDebugInfo()
|
||||
: std::string();
|
||||
if (!debugLog.empty()) {
|
||||
@@ -569,10 +585,35 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
}
|
||||
}
|
||||
if (data.is_need_rating() && _id && _accessHash) {
|
||||
Ui::show(Box<RateCallBox>(&_user->session(), _id, _accessHash));
|
||||
const auto session = &_user->session();
|
||||
const auto callId = _id;
|
||||
const auto callAccessHash = _accessHash;
|
||||
const auto box = Ui::show(Box<Ui::RateCallBox>(
|
||||
Core::App().settings().sendSubmitWay()));
|
||||
const auto sender = box->lifetime().make_state<MTP::Sender>(
|
||||
&session->mtp());
|
||||
box->sends(
|
||||
) | rpl::take(
|
||||
1 // Instead of keeping requestId.
|
||||
) | rpl::start_with_next([=](const Ui::RateCallBox::Result &r) {
|
||||
sender->request(MTPphone_SetCallRating(
|
||||
MTP_flags(0),
|
||||
MTP_inputPhoneCall(
|
||||
MTP_long(callId),
|
||||
MTP_long(callAccessHash)),
|
||||
MTP_int(r.rating),
|
||||
MTP_string(r.comment)
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
session->api().applyUpdates(updates);
|
||||
box->closeBox();
|
||||
}).fail([=] {
|
||||
box->closeBox();
|
||||
}).send();
|
||||
}, box->lifetime());
|
||||
}
|
||||
const auto reason = data.vreason();
|
||||
if (reason && reason->type() == mtpc_phoneCallDiscardReasonDisconnect) {
|
||||
if (reason
|
||||
&& reason->type() == mtpc_phoneCallDiscardReasonDisconnect) {
|
||||
LOG(("Call Info: Discarded with DISCONNECT reason."));
|
||||
}
|
||||
if (reason && reason->type() == mtpc_phoneCallDiscardReasonBusy) {
|
||||
@@ -586,7 +627,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
} return true;
|
||||
|
||||
case mtpc_phoneCallAccepted: {
|
||||
auto &data = call.c_phoneCallAccepted();
|
||||
const auto &data = call.c_phoneCallAccepted();
|
||||
if (data.vid().v != _id) {
|
||||
return false;
|
||||
}
|
||||
@@ -678,24 +719,25 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
|
||||
)).done([=](const MTPphone_PhoneCall &result) {
|
||||
Expects(result.type() == mtpc_phone_phoneCall);
|
||||
|
||||
auto &call = result.c_phone_phoneCall();
|
||||
const auto &call = result.c_phone_phoneCall();
|
||||
_user->session().data().processUsers(call.vusers());
|
||||
if (call.vphone_call().type() != mtpc_phoneCall) {
|
||||
LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()"));
|
||||
LOG(("Call Error: Expected phoneCall in response to "
|
||||
"phone.confirmCall()"));
|
||||
finish(FinishType::Failed);
|
||||
return;
|
||||
}
|
||||
|
||||
createAndStartController(call.vphone_call().c_phoneCall());
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
handleRequestError(error);
|
||||
handleRequestError(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Call::startConfirmedCall(const MTPDphoneCall &call) {
|
||||
Expects(_type == Type::Incoming);
|
||||
|
||||
auto firstBytes = bytes::make_span(call.vg_a_or_b().v);
|
||||
const auto firstBytes = bytes::make_span(call.vg_a_or_b().v);
|
||||
if (_gaHash != openssl::Sha256(firstBytes)) {
|
||||
LOG(("Call Error: Wrong g_a hash received."));
|
||||
finish(FinishType::Failed);
|
||||
@@ -703,7 +745,10 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
|
||||
}
|
||||
_ga = bytes::vector(firstBytes.begin(), firstBytes.end());
|
||||
|
||||
auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p);
|
||||
const auto computedAuthKey = MTP::CreateAuthKey(
|
||||
firstBytes,
|
||||
_randomPower,
|
||||
_dhConfig.p);
|
||||
if (computedAuthKey.empty()) {
|
||||
LOG(("Call Error: Could not compute mod-exp final."));
|
||||
finish(FinishType::Failed);
|
||||
@@ -718,22 +763,25 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) {
|
||||
|
||||
void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
_discardByTimeoutTimer.cancel();
|
||||
if (!checkCallFields(call) || _authKey.size() != 256) {
|
||||
if (!checkCallFields(call) || _authKey.size() != kAuthKeySize) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
|
||||
const auto &serverConfig = _user->session().serverConfig();
|
||||
|
||||
auto encryptionKeyValue = std::make_shared<std::array<uint8_t, 256>>();
|
||||
memcpy(encryptionKeyValue->data(), _authKey.data(), 256);
|
||||
auto encryptionKeyValue = std::make_shared<std::array<
|
||||
uint8_t,
|
||||
kAuthKeySize>>();
|
||||
memcpy(encryptionKeyValue->data(), _authKey.data(), kAuthKeySize);
|
||||
|
||||
const auto &settings = Core::App().settings();
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
tgcalls::Descriptor descriptor = {
|
||||
.config = tgcalls::Config{
|
||||
.initializationTimeout = serverConfig.callConnectTimeoutMs / 1000.,
|
||||
.initializationTimeout =
|
||||
serverConfig.callConnectTimeoutMs / 1000.,
|
||||
.receiveTimeout = serverConfig.callPacketTimeoutMs / 1000.,
|
||||
.dataSaving = tgcalls::DataSaving::Never,
|
||||
.enableP2P = call.is_p2p_allowed(),
|
||||
@@ -763,7 +811,9 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
handleControllerBarCountChange(count);
|
||||
});
|
||||
},
|
||||
.remoteMediaStateUpdated = [=](tgcalls::AudioState audio, tgcalls::VideoState video) {
|
||||
.remoteMediaStateUpdated = [=](
|
||||
tgcalls::AudioState audio,
|
||||
tgcalls::VideoState video) {
|
||||
crl::on_main(weak, [=] {
|
||||
updateRemoteMediaState(audio, video);
|
||||
});
|
||||
@@ -780,9 +830,9 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
settings.callAudioBackend()),
|
||||
};
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
const auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
const auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
|
||||
const auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
@@ -802,7 +852,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
}
|
||||
|
||||
{
|
||||
auto &settingsProxy = Core::App().settings().proxy();
|
||||
const auto &settingsProxy = Core::App().settings().proxy();
|
||||
using ProxyData = MTP::ProxyData;
|
||||
if (settingsProxy.useProxyForCalls() && settingsProxy.isEnabled()) {
|
||||
const auto &selected = settingsProxy.selected();
|
||||
@@ -862,7 +912,7 @@ void Call::handleControllerStateChange(tgcalls::State state) {
|
||||
} break;
|
||||
|
||||
case tgcalls::State::Failed: {
|
||||
auto error = _instance
|
||||
const auto error = _instance
|
||||
? QString::fromStdString(_instance->getLastError())
|
||||
: QString();
|
||||
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
|
||||
@@ -884,7 +934,7 @@ void Call::setSignalBarCount(int count) {
|
||||
|
||||
template <typename T>
|
||||
bool Call::checkCallCommonFields(const T &call) {
|
||||
auto checkFailed = [this] {
|
||||
const auto checkFailed = [this] {
|
||||
finish(FinishType::Failed);
|
||||
return false;
|
||||
};
|
||||
@@ -892,14 +942,22 @@ bool Call::checkCallCommonFields(const T &call) {
|
||||
LOG(("Call Error: Wrong call access_hash."));
|
||||
return checkFailed();
|
||||
}
|
||||
auto adminId = (_type == Type::Outgoing) ? _user->session().userId() : peerToUser(_user->id);
|
||||
auto participantId = (_type == Type::Outgoing) ? peerToUser(_user->id) : _user->session().userId();
|
||||
const auto adminId = (_type == Type::Outgoing)
|
||||
? _user->session().userId()
|
||||
: peerToUser(_user->id);
|
||||
const auto participantId = (_type == Type::Outgoing)
|
||||
? peerToUser(_user->id)
|
||||
: _user->session().userId();
|
||||
if (UserId(call.vadmin_id()) != adminId) {
|
||||
LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id().v).arg(adminId.bare));
|
||||
LOG(("Call Error: Wrong call admin_id %1, expected %2.")
|
||||
.arg(call.vadmin_id().v)
|
||||
.arg(adminId.bare));
|
||||
return checkFailed();
|
||||
}
|
||||
if (UserId(call.vparticipant_id()) != participantId) {
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id().v).arg(participantId.bare));
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2.")
|
||||
.arg(call.vparticipant_id().v)
|
||||
.arg(participantId.bare));
|
||||
return checkFailed();
|
||||
}
|
||||
return true;
|
||||
@@ -925,7 +983,8 @@ void Call::setState(State state) {
|
||||
if (_state.current() == State::Failed) {
|
||||
return;
|
||||
}
|
||||
if (_state.current() == State::FailedHangingUp && state != State::Failed) {
|
||||
if (_state.current() == State::FailedHangingUp
|
||||
&& state != State::Failed) {
|
||||
return;
|
||||
}
|
||||
if (_state.current() != state) {
|
||||
@@ -967,6 +1026,7 @@ void Call::setState(State state) {
|
||||
break;
|
||||
case State::Busy:
|
||||
_delegate->callPlaySound(Delegate::CallSound::Busy);
|
||||
_discardByTimeoutTimer.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1087,11 +1147,17 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
|
||||
|
||||
setSignalBarCount(kSignalBarFinished);
|
||||
|
||||
auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed;
|
||||
auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp;
|
||||
const auto finalState = (type == FinishType::Ended)
|
||||
? State::Ended
|
||||
: State::Failed;
|
||||
const auto hangupState = (type == FinishType::Ended)
|
||||
? State::HangingUp
|
||||
: State::FailedHangingUp;
|
||||
const auto state = _state.current();
|
||||
if (state == State::Requesting) {
|
||||
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
|
||||
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] {
|
||||
setState(finalState);
|
||||
});
|
||||
_finishAfterRequestingCall = type;
|
||||
return;
|
||||
}
|
||||
@@ -1108,11 +1174,17 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
|
||||
}
|
||||
|
||||
setState(hangupState);
|
||||
auto duration = getDurationMs() / 1000;
|
||||
auto connectionId = _instance ? _instance->getPreferredRelayId() : 0;
|
||||
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
|
||||
const auto flags = ((_videoIncoming->state() != Webrtc::VideoState::Inactive)
|
||||
|| (_videoOutgoing->state() != Webrtc::VideoState::Inactive))
|
||||
const auto duration = getDurationMs() / 1000;
|
||||
const auto connectionId = _instance
|
||||
? _instance->getPreferredRelayId()
|
||||
: 0;
|
||||
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] {
|
||||
setState(finalState);
|
||||
});
|
||||
|
||||
using Video = Webrtc::VideoState;
|
||||
const auto flags = ((_videoIncoming->state() != Video::Inactive)
|
||||
|| (_videoOutgoing->state() != Video::Inactive))
|
||||
? MTPphone_DiscardCall::Flag::f_video
|
||||
: MTPphone_DiscardCall::Flag(0);
|
||||
|
||||
@@ -1150,25 +1222,28 @@ void Call::setFailedQueued(const QString &error) {
|
||||
});
|
||||
}
|
||||
|
||||
void Call::handleRequestError(const MTP::Error &error) {
|
||||
if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_not_available(tr::now, lt_user, _user->name)));
|
||||
} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_outdated(tr::now, lt_user, _user->name)));
|
||||
} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
|
||||
Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)));
|
||||
void Call::handleRequestError(const QString &error) {
|
||||
const auto inform = (error == u"USER_PRIVACY_RESTRICTED"_q)
|
||||
? tr::lng_call_error_not_available(tr::now, lt_user, _user->name)
|
||||
: (error == u"PARTICIPANT_VERSION_OUTDATED"_q)
|
||||
? tr::lng_call_error_outdated(tr::now, lt_user, _user->name)
|
||||
: (error == u"CALL_PROTOCOL_LAYER_INVALID"_q)
|
||||
? Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)
|
||||
: QString();
|
||||
if (!inform.isEmpty()) {
|
||||
Ui::show(Box<Ui::InformBox>(inform));
|
||||
}
|
||||
finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
void Call::handleControllerError(const QString &error) {
|
||||
if (error == u"ERROR_INCOMPATIBLE"_q) {
|
||||
Ui::show(Box<InformBox>(
|
||||
Lang::Hard::CallErrorIncompatible().replace(
|
||||
"{user}",
|
||||
_user->name)));
|
||||
} else if (error == u"ERROR_AUDIO_IO"_q) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
||||
const auto inform = (error == u"ERROR_INCOMPATIBLE"_q)
|
||||
? Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)
|
||||
: (error == u"ERROR_AUDIO_IO"_q)
|
||||
? tr::lng_call_error_audio_io(tr::now)
|
||||
: QString();
|
||||
if (!inform.isEmpty()) {
|
||||
Ui::show(Box<Ui::InformBox>(inform));
|
||||
}
|
||||
finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@ public:
|
||||
[[nodiscard]] not_null<UserData*> user() const {
|
||||
return _user;
|
||||
}
|
||||
[[nodiscard]] CallId id() const {
|
||||
return _id;
|
||||
}
|
||||
[[nodiscard]] bool isIncomingWaiting() const;
|
||||
|
||||
void start(bytes::const_span random);
|
||||
@@ -215,7 +218,7 @@ private:
|
||||
Failed,
|
||||
};
|
||||
|
||||
void handleRequestError(const MTP::Error &error);
|
||||
void handleRequestError(const QString &error);
|
||||
void handleControllerError(const QString &error);
|
||||
void finish(
|
||||
FinishType type,
|
||||
@@ -255,7 +258,8 @@ private:
|
||||
MTP::Sender _api;
|
||||
Type _type = Type::Outgoing;
|
||||
rpl::variable<State> _state = State::Starting;
|
||||
rpl::variable<RemoteAudioState> _remoteAudioState = RemoteAudioState::Active;
|
||||
rpl::variable<RemoteAudioState> _remoteAudioState =
|
||||
RemoteAudioState::Active;
|
||||
rpl::variable<Webrtc::VideoState> _remoteVideoState;
|
||||
rpl::event_stream<Error> _errors;
|
||||
FinishType _finishAfterRequestingCall = FinishType::None;
|
||||
@@ -273,9 +277,8 @@ private:
|
||||
bytes::vector _gaHash;
|
||||
bytes::vector _randomPower;
|
||||
MTP::AuthKey::Data _authKey;
|
||||
MTPPhoneCallProtocol _protocol;
|
||||
|
||||
uint64 _id = 0;
|
||||
CallId _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user