Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9fc9e881 | ||
|
|
1d089366ff | ||
|
|
fe40464e33 | ||
|
|
728b1efb9a | ||
|
|
175f3d7a38 | ||
|
|
ae1fb8841a | ||
|
|
16ba20f898 | ||
|
|
d8ffc114d3 | ||
|
|
23bd76a8dd | ||
|
|
d85981cca0 | ||
|
|
7b466e0643 | ||
|
|
d984c5924d | ||
|
|
cfa3352caf | ||
|
|
05d2fc819c | ||
|
|
cb930a89ce | ||
|
|
3808874da0 | ||
|
|
3be8521b9a | ||
|
|
9fb72e1c3e | ||
|
|
e26e666135 | ||
|
|
e9196bbbb5 | ||
|
|
819ce06dfb | ||
|
|
da1168fb00 | ||
|
|
ffdcda5a90 | ||
|
|
06292e7442 | ||
|
|
5bb73d8d3d | ||
|
|
68e35b232d | ||
|
|
2843500ce3 | ||
|
|
2730ab1596 | ||
|
|
3a28be1e16 | ||
|
|
03214ab522 | ||
|
|
6b1bc1e845 | ||
|
|
1d64b53cd0 | ||
|
|
c360bb9da4 | ||
|
|
08e170a068 | ||
|
|
42d40a6f8f | ||
|
|
d93d47f2cf | ||
|
|
25470cde3c | ||
|
|
c242a61e8c | ||
|
|
286cb74620 | ||
|
|
336405b3c7 | ||
|
|
2eda5bb2d8 | ||
|
|
06618a5253 | ||
|
|
de70df0b6b | ||
|
|
cf6dbfaf55 | ||
|
|
6cbee72b8a | ||
|
|
0a0803de6f | ||
|
|
bee6a1dc06 | ||
|
|
155fcb6dde | ||
|
|
3e5e0cb9df | ||
|
|
be74a391ba | ||
|
|
d71b6effd6 | ||
|
|
5ff70315cb | ||
|
|
04c0d79ccc | ||
|
|
34c5ce16d0 | ||
|
|
e52f947f98 | ||
|
|
40e46e8480 | ||
|
|
c87802ce65 | ||
|
|
73c63cb2c7 | ||
|
|
0ead0879d7 | ||
|
|
52b5c4cbe0 | ||
|
|
a9422111a2 | ||
|
|
e5ac7a1416 | ||
|
|
99501d844d | ||
|
|
46ee5598f5 | ||
|
|
aa843ee978 | ||
|
|
ad0c93cbb1 | ||
|
|
35ff621b5b | ||
|
|
a2d2c8a208 | ||
|
|
0e1a445614 | ||
|
|
13d0d15a50 | ||
|
|
16f1875fdc | ||
|
|
4625e7613b | ||
|
|
a100048cce | ||
|
|
188070d03f | ||
|
|
23996d14d3 | ||
|
|
07c65dfd74 | ||
|
|
61741b53c3 | ||
|
|
ae74c8a6b8 | ||
|
|
4ecd1049b2 | ||
|
|
71c4cc9623 | ||
|
|
d41bd1483e | ||
|
|
0e47c6b415 | ||
|
|
4d91ab7079 | ||
|
|
dc2192d5ca | ||
|
|
1342077dcb | ||
|
|
61d0cc38b0 | ||
|
|
79f7aa703a | ||
|
|
65dd9b82c0 | ||
|
|
96bc4858c1 | ||
|
|
27fc61c676 | ||
|
|
8c7217ad56 | ||
|
|
bd42c68978 | ||
|
|
c2900db061 | ||
|
|
d7e90fec1a | ||
|
|
66e7f05df1 | ||
|
|
088fda4ed8 | ||
|
|
15d17c8b0e | ||
|
|
e6587f2556 | ||
|
|
d40687adb8 | ||
|
|
ee098d00ad | ||
|
|
e106bd143e | ||
|
|
62684ab9bb | ||
|
|
69b70cda54 | ||
|
|
b6c86fd298 | ||
|
|
d55d7f37d7 | ||
|
|
b1c122a260 | ||
|
|
bdffdea358 | ||
|
|
491ec2db7f | ||
|
|
cd4a9d7c16 | ||
|
|
7cbe158d00 | ||
|
|
1cc1f380d0 | ||
|
|
0188719d04 | ||
|
|
e6ba6050e7 | ||
|
|
889e0dc035 | ||
|
|
663db64688 | ||
|
|
2e58993181 | ||
|
|
f09a468a7c | ||
|
|
b08d9fe0b8 | ||
|
|
619f70ab22 | ||
|
|
21b502c754 | ||
|
|
8cac76931e | ||
|
|
00c915e58d | ||
|
|
8889329415 | ||
|
|
3ec3f6484f | ||
|
|
c7a1771dec | ||
|
|
320adcd389 | ||
|
|
1050447eed | ||
|
|
0af6c4b0b6 | ||
|
|
e077163322 | ||
|
|
47fdef1e38 | ||
|
|
fafea73ea7 | ||
|
|
56031a6402 | ||
|
|
5e4bc200c2 | ||
|
|
5bc6e6533f | ||
|
|
76b4e18518 | ||
|
|
994dbf9eb5 | ||
|
|
212497413c | ||
|
|
0d44736575 | ||
|
|
4c707bff29 | ||
|
|
25bbde2739 | ||
|
|
c74e240d30 | ||
|
|
7b277aa770 | ||
|
|
f93527442d | ||
|
|
35610da750 | ||
|
|
21228783da | ||
|
|
b323e5ffcf | ||
|
|
7c979144fc | ||
|
|
fd85efa9ba | ||
|
|
22da48d231 | ||
|
|
3343880ed0 | ||
|
|
df73bda1ff | ||
|
|
75a782cced | ||
|
|
0e126e2550 | ||
|
|
cffb615a4d | ||
|
|
7ab3be3631 | ||
|
|
591488c497 | ||
|
|
ae54c8af6a | ||
|
|
30c86523ff | ||
|
|
9a6e571154 | ||
|
|
13a497cf5d | ||
|
|
6463890b11 | ||
|
|
073b5b106c | ||
|
|
0cd8cc67c5 | ||
|
|
f528a240d9 | ||
|
|
a814c3f428 | ||
|
|
81d052adfc | ||
|
|
822c1cafd2 | ||
|
|
c08a148baf | ||
|
|
a38a94ed9c | ||
|
|
4d579f873c | ||
|
|
56c8327746 | ||
|
|
0e6d4291a2 | ||
|
|
8ca622d077 | ||
|
|
4d24f28fd0 | ||
|
|
2b3469ef22 | ||
|
|
03a868a6e3 | ||
|
|
12db51fe75 | ||
|
|
ce5579e8f9 | ||
|
|
a16b7fbb83 | ||
|
|
9f6f7f7c9b | ||
|
|
a82d1e863e | ||
|
|
26d97a3636 | ||
|
|
7b8e421996 | ||
|
|
2bc2a0e459 | ||
|
|
7cb4b4f8ab | ||
|
|
b439ecce16 | ||
|
|
a33a4c0589 | ||
|
|
5278e2201f | ||
|
|
3bd6b2268f | ||
|
|
a0a13c3b86 | ||
|
|
0052c7938f | ||
|
|
a14db3e492 | ||
|
|
7979b3b6c8 | ||
|
|
3f25e92afd | ||
|
|
3d1cddaca5 | ||
|
|
eeecc42c25 | ||
|
|
e22ecafc1d | ||
|
|
ba41da7b28 | ||
|
|
9cfbccf9e7 | ||
|
|
2b6f50e114 | ||
|
|
d2f57b72c3 | ||
|
|
85ac983a27 |
3
.gitattributes
vendored
@@ -4,3 +4,6 @@
|
||||
# Ensure diffs have LF endings
|
||||
*.diff text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
||||
# Ensure lottie animations are treated as binary files
|
||||
*.lottie binary
|
||||
|
||||
4
.github/workflows/linux.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0}
|
||||
shell: scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -65,6 +65,8 @@ jobs:
|
||||
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
|
||||
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
|
||||
- "DESKTOP_APP_DISABLE_GTK_INTEGRATION"
|
||||
- "LIBTGVOIP_DISABLE_ALSA"
|
||||
- "LIBTGVOIP_DISABLE_PULSEAUDIO"
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
|
||||
9
.github/workflows/win.yml
vendored
@@ -114,6 +114,11 @@ jobs:
|
||||
- name: Choco installs.
|
||||
run: choco install --no-progress -y nasm yasm jom ninja
|
||||
|
||||
- name: NuGet sources.
|
||||
run: |
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: Patches.
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -279,6 +284,10 @@ jobs:
|
||||
msbuild -m opus.sln /property:Configuration=Debug /property:Platform="Win32"
|
||||
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
|
||||
|
||||
echo "Workaround for FFmpeg."
|
||||
copy Win32\Release\m.lib Win32\Release\ssp.lib
|
||||
copy Win32\Release\m.lib Win32\Debug\ssp.lib
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v2
|
||||
|
||||
2
.gitignore
vendored
@@ -48,3 +48,5 @@ stage
|
||||
/Linux/
|
||||
/Telegram/Makefile
|
||||
*.*~
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
|
||||
3
.gitmodules
vendored
@@ -88,3 +88,6 @@
|
||||
[submodule "Telegram/ThirdParty/tgcalls"]
|
||||
path = Telegram/ThirdParty/tgcalls
|
||||
url = https://github.com/TelegramMessenger/tgcalls.git
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
|
||||
@@ -15,7 +15,8 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
|
||||
|
||||
The latest version is available for
|
||||
|
||||
* [Windows 7 and above](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [Windows 7 and above (64 bit)](https://telegram.org/dl/desktop/win64) ([portable](https://telegram.org/dl/desktop/win64_portable))
|
||||
* [Windows 7 and above (32 bit)](https://telegram.org/dl/desktop/win) ([portable](https://telegram.org/dl/desktop/win_portable))
|
||||
* [macOS 10.12 and above](https://telegram.org/dl/desktop/mac)
|
||||
* [Linux static build for 64 bit](https://telegram.org/dl/desktop/linux)
|
||||
* [Snap](https://snapcraft.io/telegram-desktop)
|
||||
@@ -62,7 +63,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
|
||||
## Build instructions
|
||||
|
||||
* [Visual Studio 2019][msvc]
|
||||
* Visual Studio 2019 [(32 bits)][msvc32] [(64 bits)][msvc64]
|
||||
* [Xcode 12][xcode]
|
||||
* [CMake on GNU/Linux][cmake]
|
||||
|
||||
@@ -72,7 +73,8 @@ Version **1.8.15** was the last that supports older systems
|
||||
[telegram_api]: https://core.telegram.org
|
||||
[telegram_proto]: https://core.telegram.org/mtproto
|
||||
[license]: LICENSE
|
||||
[msvc]: docs/building-msvc.md
|
||||
[msvc32]: docs/building-msvc.md
|
||||
[msvc64]: docs/building-msvc-x64.md
|
||||
[xcode]: docs/building-xcode.md
|
||||
[xcode_old]: docs/building-xcode-old.md
|
||||
[cmake]: docs/building-cmake.md
|
||||
|
||||
@@ -19,6 +19,7 @@ add_subdirectory(lib_storage)
|
||||
add_subdirectory(lib_lottie)
|
||||
add_subdirectory(lib_qr)
|
||||
add_subdirectory(lib_webrtc)
|
||||
add_subdirectory(lib_webview)
|
||||
add_subdirectory(codegen)
|
||||
|
||||
get_filename_component(src_loc SourceFiles REALPATH)
|
||||
@@ -26,6 +27,7 @@ get_filename_component(res_loc Resources REALPATH)
|
||||
|
||||
include(cmake/telegram_options.cmake)
|
||||
include(cmake/lib_ffmpeg.cmake)
|
||||
include(cmake/lib_stripe.cmake)
|
||||
include(cmake/lib_tgvoip.cmake)
|
||||
include(cmake/lib_tgcalls.cmake)
|
||||
include(cmake/td_export.cmake)
|
||||
@@ -35,6 +37,11 @@ include(cmake/td_scheme.cmake)
|
||||
include(cmake/td_ui.cmake)
|
||||
include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
@@ -55,7 +62,9 @@ PRIVATE
|
||||
desktop-app::lib_storage
|
||||
desktop-app::lib_lottie
|
||||
desktop-app::lib_qr
|
||||
desktop-app::lib_webview
|
||||
desktop-app::lib_ffmpeg
|
||||
desktop-app::lib_stripe
|
||||
desktop-app::external_lz4
|
||||
desktop-app::external_rlottie
|
||||
desktop-app::external_zlib
|
||||
@@ -70,7 +79,12 @@ PRIVATE
|
||||
desktop-app::external_xxhash
|
||||
)
|
||||
|
||||
if (LINUX)
|
||||
if (WIN32)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_webview_winrt
|
||||
)
|
||||
elseif (LINUX)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
@@ -249,8 +263,6 @@ PRIVATE
|
||||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/single_choice_box.cpp
|
||||
boxes/single_choice_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
@@ -383,8 +395,6 @@ PRIVATE
|
||||
data/data_cloud_file.h
|
||||
data/data_cloud_themes.cpp
|
||||
data/data_cloud_themes.h
|
||||
data/data_countries.cpp
|
||||
data/data_countries.h
|
||||
data/data_document.cpp
|
||||
data/data_document.h
|
||||
data/data_document_media.cpp
|
||||
@@ -415,6 +425,8 @@ PRIVATE
|
||||
data/data_notify_settings.h
|
||||
data/data_peer.cpp
|
||||
data/data_peer.h
|
||||
data/data_peer_id.cpp
|
||||
data/data_peer_id.h
|
||||
data/data_peer_values.cpp
|
||||
data/data_peer_values.h
|
||||
data/data_photo.cpp
|
||||
@@ -796,8 +808,6 @@ PRIVATE
|
||||
passport/passport_panel.h
|
||||
passport/passport_panel_controller.cpp
|
||||
passport/passport_panel_controller.h
|
||||
passport/passport_panel_details_row.cpp
|
||||
passport/passport_panel_details_row.h
|
||||
passport/passport_panel_edit_contact.cpp
|
||||
passport/passport_panel_edit_contact.h
|
||||
passport/passport_panel_edit_document.cpp
|
||||
@@ -808,6 +818,10 @@ PRIVATE
|
||||
passport/passport_panel_form.h
|
||||
passport/passport_panel_password.cpp
|
||||
passport/passport_panel_password.h
|
||||
payments/payments_checkout_process.cpp
|
||||
payments/payments_checkout_process.h
|
||||
payments/payments_form.cpp
|
||||
payments/payments_form.h
|
||||
platform/linux/linux_desktop_environment.cpp
|
||||
platform/linux/linux_desktop_environment.h
|
||||
platform/linux/linux_gdk_helper.cpp
|
||||
@@ -821,6 +835,8 @@ PRIVATE
|
||||
platform/linux/linux_gtk_integration.h
|
||||
platform/linux/linux_gtk_open_with_dialog.cpp
|
||||
platform/linux/linux_gtk_open_with_dialog.h
|
||||
platform/linux/linux_mpris_support.cpp
|
||||
platform/linux/linux_mpris_support.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
@@ -1005,8 +1021,6 @@ PRIVATE
|
||||
ui/widgets/level_meter.h
|
||||
ui/widgets/multi_select.cpp
|
||||
ui/widgets/multi_select.h
|
||||
ui/widgets/separate_panel.cpp
|
||||
ui/widgets/separate_panel.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/empty_userpic.cpp
|
||||
@@ -1022,8 +1036,6 @@ PRIVATE
|
||||
ui/search_field_controller.h
|
||||
ui/special_buttons.cpp
|
||||
ui/special_buttons.h
|
||||
ui/special_fields.cpp
|
||||
ui/special_fields.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
window/main_window.cpp
|
||||
@@ -1113,6 +1125,8 @@ if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_gsd_media_keys.cpp
|
||||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_mpris_support.cpp
|
||||
platform/linux/linux_mpris_support.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_xdp_file_dialog.cpp
|
||||
|
||||
BIN
Telegram/Resources/icons/calls/group_calls_share.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
Telegram/Resources/icons/calls/group_calls_share@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/calls/group_calls_share@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
1
Telegram/Resources/icons/calls/hands.lottie
Normal file
1
Telegram/Resources/icons/calls/voice.lottie
Normal file
BIN
Telegram/Resources/icons/inline_button_card.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
Telegram/Resources/icons/inline_button_card@2x.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
Telegram/Resources/icons/inline_button_card@3x.png
Normal file
|
After Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 768 B |
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 428 B |
|
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 550 B |
BIN
Telegram/Resources/icons/payments/payment_address.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
Telegram/Resources/icons/payments/payment_address@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/payments/payment_address@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_card.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
Telegram/Resources/icons/payments/payment_card@2x.png
Normal file
|
After Width: | Height: | Size: 560 B |
BIN
Telegram/Resources/icons/payments/payment_card@3x.png
Normal file
|
After Width: | Height: | Size: 985 B |
BIN
Telegram/Resources/icons/payments/payment_email.png
Normal file
|
After Width: | Height: | Size: 949 B |
BIN
Telegram/Resources/icons/payments/payment_email@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/payments/payment_email@3x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Telegram/Resources/icons/payments/payment_name.png
Normal file
|
After Width: | Height: | Size: 473 B |
BIN
Telegram/Resources/icons/payments/payment_name@2x.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
Telegram/Resources/icons/payments/payment_name@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone.png
Normal file
|
After Width: | Height: | Size: 711 B |
BIN
Telegram/Resources/icons/payments/payment_phone@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/payments/payment_shipping.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@2x.png
Normal file
|
After Width: | Height: | Size: 1022 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -216,6 +216,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_code_desc" = "We have sent you a message with activation\ncode to your phone. Please enter it below.";
|
||||
"lng_code_from_telegram" = "Please enter the code you've just received\nin your previous **Telegram** app.";
|
||||
"lng_code_no_telegram" = "Send code via SMS";
|
||||
"lng_code_register_phone" = "If you already signed up for Telegram, please enter the code which was sent to your mobile app.\n\nIf you haven’t signed up yet, please register from your phone or tablet first.";
|
||||
"lng_code_call" = "Telegram will call you in {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Requesting a call from Telegram...";
|
||||
"lng_code_called" = "Telegram dialed your number";
|
||||
@@ -453,6 +454,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
"lng_settings_security_bots" = "Bots and websites";
|
||||
"lng_settings_clear_payment_info" = "Clear Payment and Shipping Info";
|
||||
|
||||
"lng_clear_payment_info_title" = "Clear payment info";
|
||||
"lng_clear_payment_info_sure" = "Are you sure you want to clear your payment and shipping info?";
|
||||
"lng_clear_payment_info_shipping" = "Shipping info";
|
||||
"lng_clear_payment_info_payment" = "Payment info";
|
||||
"lng_clear_payment_info_clear" = "Clear";
|
||||
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
|
||||
|
||||
"lng_settings_auto_night_mode" = "Auto-Night mode";
|
||||
"lng_settings_auto_night_enabled" = "Match the system settings";
|
||||
@@ -1097,8 +1107,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_invite_user_chat" = "the voice chat";
|
||||
"lng_action_invite_users_and_one" = "{accumulated}, {user}";
|
||||
"lng_action_invite_users_and_last" = "{accumulated} and {user}";
|
||||
"lng_action_group_call_started" = "{from} started {chat}";
|
||||
"lng_action_group_call_started_chat" = "a voice chat";
|
||||
"lng_action_group_call_started_group" = "{from} started a voice chat";
|
||||
"lng_action_group_call_started_channel" = "Voice chat started";
|
||||
"lng_action_group_call_scheduled_group" = "{from} scheduled a voice chat for {date}";
|
||||
"lng_action_group_call_scheduled_channel" = "Voice chat scheduled for {date}";
|
||||
"lng_action_group_call_finished" = "Voice chat finished ({duration})";
|
||||
"lng_action_add_user" = "{from} added {user}";
|
||||
"lng_action_add_users_many" = "{from} added {users}";
|
||||
@@ -1854,11 +1866,68 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_theme_editor_menu_show" = "Show palette file";
|
||||
|
||||
"lng_payments_not_supported" = "Sorry, Telegram Desktop doesn't support payments yet. Please use one of our mobile apps to do this.";
|
||||
"lng_payments_webview_no_card" = "Unfortunately, you can't add a new card with current system configuration.";
|
||||
"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration.";
|
||||
"lng_payments_webview_install_edge" = "Please install {link}.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK 4 (webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_switch_mutter" = "Qt's window embedding doesn't work well with Mutter window manager. Please switch to another window manager or desktop environment.";
|
||||
"lng_payments_webview_switch_wayland" = "There is no way to embed WebView window on Wayland. Please switch to X11.";
|
||||
"lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
|
||||
"lng_payments_receipt_label" = "Receipt";
|
||||
"lng_payments_receipt_label_test" = "Test receipt";
|
||||
"lng_payments_invoice_label" = "Invoice";
|
||||
"lng_payments_invoice_label_test" = "Test invoice";
|
||||
"lng_payments_receipt_button" = "Receipt";
|
||||
"lng_payments_success" = "You paid {amount} for {title}.";
|
||||
|
||||
"lng_payments_checkout_title" = "Checkout";
|
||||
"lng_payments_receipt_title" = "Receipt";
|
||||
"lng_payments_total_label" = "Total";
|
||||
"lng_payments_date_label" = "Paid";
|
||||
"lng_payments_pay_amount" = "Pay {amount}";
|
||||
"lng_payments_payment_method" = "Payment Method";
|
||||
"lng_payments_new_card" = "New Card...";
|
||||
"lng_payments_shipping_address" = "Shipping Address";
|
||||
"lng_payments_address_street1" = "Address 1";
|
||||
"lng_payments_address_street2" = "Address 2";
|
||||
"lng_payments_address_city" = "City";
|
||||
"lng_payments_address_state" = "State";
|
||||
"lng_payments_address_country" = "Country";
|
||||
"lng_payments_address_postcode" = "Postcode";
|
||||
|
||||
"lng_payments_shipping_method" = "Shipping Method";
|
||||
"lng_payments_info_name" = "Name";
|
||||
"lng_payments_info_email" = "Email";
|
||||
"lng_payments_info_phone" = "Phone";
|
||||
"lng_payments_to_provider_phone_email" = "Phone and Email will be passed to {provider} as billing info.";
|
||||
"lng_payments_to_provider_email" = "Email will be passed to {provider} as billing info.";
|
||||
"lng_payments_to_provider_phone" = "Phone will be passed to {provider} as billing info.";
|
||||
"lng_payments_processed_by" = "Processed by {provider}";
|
||||
"lng_payments_warning_title" = "Warning";
|
||||
"lng_payments_warning_body" = "Neither Telegram, nor {bot1} will have access to your credit card information. Credit card details will be handled only by the payment system, {provider}.\n\nPayments will go directly to the developer of {bot2}. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of {bot3} or your bank.";
|
||||
"lng_payments_shipping_address_title" = "Shipping Information";
|
||||
"lng_payments_card_title" = "New Card";
|
||||
"lng_payments_card_number" = "Card Number";
|
||||
"lng_payments_card_cvc" = "CVC";
|
||||
"lng_payments_card_expire_date" = "MM / YY";
|
||||
"lng_payments_card_holder" = "Cardholder name";
|
||||
"lng_payments_billing_address" = "Billing Information";
|
||||
"lng_payments_billing_country" = "Country";
|
||||
"lng_payments_billing_zip_code" = "Zip Code";
|
||||
"lng_payments_save_information" = "Save Information for future use";
|
||||
"lng_payments_need_password" = "You can save your payment information for future use. Please turn on Two-Step Verification to enable this.";
|
||||
"lng_payments_password_title" = "Payment Confirmation";
|
||||
"lng_payments_password_description" = "Your card {card} is on file. To pay with this card, please enter your 2-Step-Verification password.";
|
||||
"lng_payments_password_submit" = "Pay";
|
||||
"lng_payments_tips_label" = "Tip (Optional)";
|
||||
"lng_payments_tips_box_title" = "Add Tip";
|
||||
"lng_payments_tips_max" = "Max possible tip amount: {amount}";
|
||||
|
||||
"lng_payments_shipping_not_available" = "Shipping to the selected country is not available.";
|
||||
"lng_payments_card_declined" = "Your card was declined.";
|
||||
"lng_payments_payment_failed" = "Payment failed. Your card has not been billed.";
|
||||
"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed.";
|
||||
"lng_payments_already_paid" = "You have already paid for this item.";
|
||||
|
||||
"lng_call_status_incoming" = "is calling you...";
|
||||
"lng_call_status_connecting" = "connecting...";
|
||||
@@ -1925,6 +1994,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_inactive" = "listening";
|
||||
"lng_group_call_raised_hand_status" = "wants to speak";
|
||||
"lng_group_call_settings" = "Settings";
|
||||
"lng_group_call_share_button" = "Share";
|
||||
"lng_group_call_unmute" = "Unmute";
|
||||
"lng_group_call_unmute_sub" = "or hold spacebar to talk";
|
||||
"lng_group_call_you_are_live" = "You are Live";
|
||||
@@ -1937,8 +2007,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_leave" = "Leave";
|
||||
"lng_group_call_leave_title" = "Leave voice chat";
|
||||
"lng_group_call_leave_sure" = "Are you sure you want to leave this voice chat?";
|
||||
"lng_group_call_close" = "Close";
|
||||
"lng_group_call_close_sure" = "Voice chat is scheduled. You can abort it or just close this panel.";
|
||||
"lng_group_call_also_cancel" = "Abort voice chat";
|
||||
"lng_group_call_leave_to_other_sure" = "Do you want to leave your active voice chat and join a voice chat in this group?";
|
||||
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
|
||||
"lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?";
|
||||
"lng_group_call_join_sure_personal" = "Are you sure you want to join this voice chat as your personal account?";
|
||||
"lng_group_call_also_end" = "End voice chat";
|
||||
"lng_group_call_settings_title" = "Settings";
|
||||
"lng_group_call_invite" = "Invite Member";
|
||||
@@ -1965,6 +2040,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_copy_speaker_link" = "Copy Speaker Link";
|
||||
"lng_group_call_copy_listener_link" = "Copy Listener Link";
|
||||
"lng_group_call_end" = "End Voice Chat";
|
||||
"lng_group_call_cancel" = "Abort Voice Chat";
|
||||
"lng_group_call_join" = "Join";
|
||||
"lng_group_call_join_confirm" = "Do you want to join the voice chat {chat}?";
|
||||
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
|
||||
@@ -1980,6 +2056,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_context_remove_hand" = "Cancel request to speak";
|
||||
"lng_group_call_context_mute_for_me" = "Mute for me";
|
||||
"lng_group_call_context_unmute_for_me" = "Unmute for me";
|
||||
"lng_group_call_context_remove" = "Remove";
|
||||
"lng_group_call_remove_channel" = "Remove {channel} from the voice chat?";
|
||||
"lng_group_call_duration_days#one" = "{count} day";
|
||||
"lng_group_call_duration_days#other" = "{count} days";
|
||||
"lng_group_call_duration_hours#one" = "{count} hour";
|
||||
@@ -1997,6 +2075,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_join_as_header" = "Join Voice Chat as...";
|
||||
"lng_group_call_display_as_header" = "Display me as...";
|
||||
"lng_group_call_join_as_about" = "Choose whether you want to be displayed as your personal account or as your channel.";
|
||||
"lng_group_call_or_schedule" = "You can also {link}.";
|
||||
"lng_group_call_schedule" = "schedule a voice chat";
|
||||
"lng_group_call_schedule_title" = "Schedule Voice Chat";
|
||||
"lng_group_call_schedule_notified_group" = "The members of the group will be notified that the voice chat will start in {duration}.";
|
||||
"lng_group_call_schedule_notified_channel" = "The subscribers of the channel will be notified that the voice chat will start in {duration}.";
|
||||
"lng_group_call_scheduled_status" = "Scheduled";
|
||||
"lng_group_call_scheduled_title" = "Scheduled Voice Chat";
|
||||
"lng_group_call_starts_short" = "Starts {when}";
|
||||
"lng_group_call_starts" = "Voice Chat starts {when}";
|
||||
"lng_group_call_starts_today" = "today at {time}";
|
||||
"lng_group_call_starts_tomorrow" = "tomorrow at {time}";
|
||||
"lng_group_call_starts_date" = "{date} at {time}";
|
||||
"lng_group_call_starts_in" = "Starts in";
|
||||
"lng_group_call_late_by" = "Late by";
|
||||
"lng_group_call_starts_short_today" = "Today, {time}";
|
||||
"lng_group_call_starts_short_tomorrow" = "Tomorrow, {time}";
|
||||
"lng_group_call_starts_short_date" = "{date}, {time}";
|
||||
"lng_group_call_start_now" = "Start Now";
|
||||
"lng_group_call_start_now_sure" = "Are you sure you want to start the voice chat now?";
|
||||
"lng_group_call_set_reminder" = "Set Reminder";
|
||||
"lng_group_call_cancel_reminder" = "Cancel Reminder";
|
||||
"lng_group_call_join_as_personal" = "personal account";
|
||||
"lng_group_call_edit_title" = "Edit voice chat title";
|
||||
"lng_group_call_switch_done" = "Members of this voice chat will now see you as **{user}**";
|
||||
@@ -2222,6 +2321,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_stopped_poll" = "{from} stopped poll:";
|
||||
"lng_admin_log_invited" = "invited {user}";
|
||||
"lng_admin_log_banned" = "banned {user}";
|
||||
"lng_admin_log_unbanned" = "unbanned {user}";
|
||||
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
|
||||
"lng_admin_log_promoted" = "changed privileges for {user}";
|
||||
"lng_admin_log_transferred" = "transferred ownership to {user}";
|
||||
@@ -2469,6 +2569,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_export_state_chats_list" = "Processing chats...";
|
||||
"lng_export_state_chats" = "Chats";
|
||||
"lng_export_state_ready_progress" = "{ready} / {total}";
|
||||
"lng_export_skip_file" = "Skip this file";
|
||||
"lng_export_progress" = "You can close this window now. Please don't quit Telegram until the data export is completed.";
|
||||
"lng_export_stop" = "Stop";
|
||||
"lng_export_sure_stop" = "Are you sure you want to stop exporting your data?\n\nIf you do, you'll need to start over.";
|
||||
|
||||
@@ -59,9 +59,8 @@
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
<file alias="icons/calls/active_hand.json">../../icons/calls/active_hand.json</file>
|
||||
<file alias="icons/calls/hand_muted_active.json">../../icons/calls/hand_muted_active.json</file>
|
||||
<file alias="icons/calls/raised_hand.json">../../icons/calls/raised_hand.json</file>
|
||||
<file alias="icons/calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
<file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
|
||||
</qresource>
|
||||
<qresource prefix="/qt-project.org">
|
||||
<file>../qmime/freedesktop.org.xml</file>
|
||||
|
||||
@@ -68,7 +68,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro
|
||||
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||
inputMediaInvoice#f4e096c3 flags:# multiple_allowed:flags.1?true can_forward:flags.2?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||
inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
|
||||
inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
@@ -90,8 +90,8 @@ inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
|
||||
inputTakeoutFileLocation#29be5899 = InputFileLocation;
|
||||
inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
|
||||
inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
|
||||
inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
|
||||
inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
|
||||
inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation;
|
||||
inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation;
|
||||
inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation;
|
||||
|
||||
peerUser#9db1bc6d user_id:int = Peer;
|
||||
@@ -113,7 +113,7 @@ userEmpty#200250ba id:int = User;
|
||||
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
|
||||
|
||||
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||
userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
|
||||
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
|
||||
|
||||
userStatusEmpty#9d05049 = UserStatus;
|
||||
userStatusOnline#edb93949 expires:int = UserStatus;
|
||||
@@ -139,7 +139,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?
|
||||
chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
|
||||
|
||||
chatPhotoEmpty#37c1011c = ChatPhoto;
|
||||
chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
|
||||
chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
|
||||
|
||||
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
|
||||
message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;
|
||||
@@ -186,6 +186,7 @@ messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int =
|
||||
messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction;
|
||||
messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector<int> = MessageAction;
|
||||
messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
|
||||
messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = 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;
|
||||
@@ -194,10 +195,10 @@ photoEmpty#2331b22d id:long = Photo;
|
||||
photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> video_sizes:flags.1?Vector<VideoSize> dc_id:int = Photo;
|
||||
|
||||
photoSizeEmpty#e17e23c type:string = PhotoSize;
|
||||
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
|
||||
photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
|
||||
photoSize#75c78e60 type:string w:int h:int size:int = PhotoSize;
|
||||
photoCachedSize#21e1ad6 type:string w:int h:int bytes:bytes = PhotoSize;
|
||||
photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
|
||||
photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector<int> = PhotoSize;
|
||||
photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector<int> = PhotoSize;
|
||||
photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize;
|
||||
|
||||
geoPointEmpty#1117dd5f = GeoPoint;
|
||||
@@ -554,7 +555,7 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
|
||||
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
|
||||
|
||||
stickerSet#40e237a8 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
|
||||
stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
|
||||
|
||||
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
|
||||
|
||||
@@ -620,8 +621,8 @@ channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
|
||||
channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
|
||||
channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
|
||||
channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
|
||||
channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
|
||||
channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant;
|
||||
channelParticipantBanned#50a1dfd6 flags:# left:flags.0?true peer:Peer kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
|
||||
channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant;
|
||||
|
||||
channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
|
||||
channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
|
||||
@@ -632,10 +633,10 @@ channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
|
||||
channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
|
||||
channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter;
|
||||
|
||||
channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
|
||||
channels.channelParticipants#9ab0feaf count:int participants:Vector<ChannelParticipant> chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipants;
|
||||
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
|
||||
|
||||
channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
|
||||
channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipant;
|
||||
|
||||
help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
|
||||
|
||||
@@ -648,6 +649,7 @@ inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:f
|
||||
inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaInvoice#d5348d85 flags:# multiple_allowed:flags.1?true can_forward:flags.3?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
|
||||
inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
@@ -659,6 +661,7 @@ botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string ent
|
||||
botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
|
||||
botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
|
||||
botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
|
||||
@@ -792,7 +795,7 @@ dataJSON#7d748d04 data:string = DataJSON;
|
||||
|
||||
labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
|
||||
|
||||
invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
|
||||
invoice#cd886e0 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector<long> = Invoice;
|
||||
|
||||
paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge;
|
||||
|
||||
@@ -812,14 +815,14 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w
|
||||
|
||||
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
|
||||
|
||||
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
|
||||
payments.paymentForm#8d0b2415 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
|
||||
|
||||
payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
|
||||
|
||||
payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
|
||||
payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;
|
||||
|
||||
payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
|
||||
payments.paymentReceipt#10b555d0 flags:# date:int bot_id:int provider_id:int title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
|
||||
|
||||
payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
|
||||
|
||||
@@ -1088,8 +1091,6 @@ emojiURL#a575739d url:string = EmojiURL;
|
||||
|
||||
emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage;
|
||||
|
||||
fileLocationToBeDeprecated#bc7fc6cd volume_id:long local_id:int = FileLocation;
|
||||
|
||||
folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder;
|
||||
|
||||
inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer;
|
||||
@@ -1169,7 +1170,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
|
||||
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
|
||||
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
|
||||
|
||||
videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
|
||||
videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
|
||||
|
||||
statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster;
|
||||
|
||||
@@ -1203,11 +1204,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
|
||||
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
|
||||
groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall;
|
||||
|
||||
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
|
||||
|
||||
groupCallParticipant#19adba89 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long = GroupCallParticipant;
|
||||
groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant;
|
||||
|
||||
phone.groupCall#9e727aad call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string chats:Vector<Chat> users:Vector<User> = phone.GroupCall;
|
||||
|
||||
@@ -1557,7 +1558,7 @@ channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = mes
|
||||
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
|
||||
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
|
||||
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
|
||||
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
|
||||
channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
|
||||
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
|
||||
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
|
||||
channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
|
||||
@@ -1573,7 +1574,7 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
|
||||
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
|
||||
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
|
||||
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
|
||||
channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
|
||||
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
|
||||
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
|
||||
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
|
||||
@@ -1592,10 +1593,10 @@ bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
|
||||
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
|
||||
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
|
||||
|
||||
payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
|
||||
payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||
payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
|
||||
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||
payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult;
|
||||
payments.getSavedInfo#227d824b = payments.SavedInfo;
|
||||
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
|
||||
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
|
||||
@@ -1615,7 +1616,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati
|
||||
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
|
||||
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
|
||||
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
|
||||
phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates;
|
||||
phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
|
||||
phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
|
||||
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
|
||||
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
|
||||
@@ -1629,6 +1630,9 @@ phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGro
|
||||
phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates;
|
||||
phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers;
|
||||
phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite;
|
||||
phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates;
|
||||
phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates;
|
||||
phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool;
|
||||
|
||||
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
|
||||
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
|
||||
@@ -1645,4 +1649,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 125
|
||||
// LAYER 128
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.6.7.0" />
|
||||
Version="2.7.4.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 2,6,7,0
|
||||
PRODUCTVERSION 2,6,7,0
|
||||
FILEVERSION 2,7,4,0
|
||||
PRODUCTVERSION 2,7,4,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.6.7.0"
|
||||
VALUE "FileVersion", "2.7.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.7.0"
|
||||
VALUE "ProductVersion", "2.7.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,7,0
|
||||
PRODUCTVERSION 2,6,7,0
|
||||
FILEVERSION 2,7,4,0
|
||||
PRODUCTVERSION 2,7,4,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.6.7.0"
|
||||
VALUE "FileVersion", "2.7.4.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.7.0"
|
||||
VALUE "ProductVersion", "2.7.4.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -54,7 +54,7 @@ JoinedByLinkSlice ParseJoinedByLinkSlice(
|
||||
for (const auto &importer : data.vimporters().v) {
|
||||
importer.match([&](const MTPDchatInviteImporter &data) {
|
||||
result.users.push_back({
|
||||
.user = owner.user(data.vuser_id().v),
|
||||
.user = owner.user(data.vuser_id()),
|
||||
.date = data.vdate().v,
|
||||
});
|
||||
});
|
||||
@@ -623,7 +623,7 @@ auto InviteLinks::parse(
|
||||
return invite.match([&](const MTPDchatInviteExported &data) {
|
||||
return Link{
|
||||
.link = qs(data.vlink()),
|
||||
.admin = peer->session().data().user(data.vadmin_id().v),
|
||||
.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(),
|
||||
|
||||
@@ -37,7 +37,7 @@ Key ExtractKey(const QString &query) {
|
||||
const auto params = parse();
|
||||
const auto channel = params.value("channel");
|
||||
const auto post = params.value("post").toInt();
|
||||
return (channel.toInt() && post) ? Key{ channel, post } : Key();
|
||||
return (channel.toULongLong() && post) ? Key{ channel, post } : Key();
|
||||
} else if (check.startsWith(qstr("tg://resolve"), Qt::CaseInsensitive)) {
|
||||
const auto params = parse();
|
||||
const auto domain = params.value("domain");
|
||||
@@ -112,7 +112,7 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupByChannel(
|
||||
&& received.messageIds.front() == postId) {
|
||||
_cache.emplace(
|
||||
_requestKey,
|
||||
FullMsgId(channel->bareId(), postId));
|
||||
FullMsgId(peerToChannel(channel->id), postId));
|
||||
ready();
|
||||
} else {
|
||||
fail();
|
||||
@@ -142,7 +142,7 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookupById(
|
||||
_requestId = _session->api().request(MTPchannels_GetChannels(
|
||||
MTP_vector<MTPInputChannel>(
|
||||
1,
|
||||
MTP_inputChannel(MTP_int(channelId), MTP_long(0)))
|
||||
MTP_inputChannel(MTP_int(channelId.bare), MTP_long(0))) // #TODO ids
|
||||
)).done([=](const MTPmessages_Chats &result) {
|
||||
result.match([&](const auto &data) {
|
||||
const auto peer = _session->data().processChats(data.vchats());
|
||||
@@ -212,7 +212,7 @@ std::optional<HistoryItem*> SingleMessageSearch::performLookup(
|
||||
if (!_requestKey.domainOrId[0].isDigit()) {
|
||||
return performLookupByUsername(_requestKey.domainOrId, ready);
|
||||
}
|
||||
const auto channelId = _requestKey.domainOrId.toInt();
|
||||
const auto channelId = ChannelId(_requestKey.domainOrId.toULongLong());
|
||||
return performLookupById(channelId, ready);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,15 +35,17 @@ EntitiesInText EntitiesFromMTP(
|
||||
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityMentionName: {
|
||||
const auto &d = entity.c_messageEntityMentionName();
|
||||
const auto userId = UserId(d.vuser_id());
|
||||
const auto data = [&] {
|
||||
if (session) {
|
||||
if (const auto user = session->data().userLoaded(d.vuser_id().v)) {
|
||||
if (const auto user = session->data().userLoaded(userId)) {
|
||||
return MentionNameDataFromFields({
|
||||
d.vuser_id().v,
|
||||
user->accessHash() });
|
||||
userId.bare,
|
||||
user->accessHash()
|
||||
});
|
||||
}
|
||||
}
|
||||
return MentionNameDataFromFields(d.vuser_id().v);
|
||||
return MentionNameDataFromFields(userId.bare);
|
||||
}();
|
||||
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
||||
} break;
|
||||
@@ -51,10 +53,11 @@ EntitiesInText EntitiesFromMTP(
|
||||
const auto &d = entity.c_inputMessageEntityMentionName();
|
||||
const auto data = [&] {
|
||||
if (session && d.vuser_id().type() == mtpc_inputUserSelf) {
|
||||
return MentionNameDataFromFields(session->userId());
|
||||
return MentionNameDataFromFields(session->userId().bare);
|
||||
} else if (d.vuser_id().type() == mtpc_inputUser) {
|
||||
auto &user = d.vuser_id().c_inputUser();
|
||||
return MentionNameDataFromFields({ user.vuser_id().v, user.vaccess_hash().v });
|
||||
const auto userId = UserId(user.vuser_id());
|
||||
return MentionNameDataFromFields({ userId.bare, user.vaccess_hash().v });
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
@@ -110,7 +113,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
case EntityType::MentionName: {
|
||||
auto inputUser = [&](const QString &data) -> MTPInputUser {
|
||||
auto fields = MentionNameDataToFields(data);
|
||||
if (session && fields.userId == session->userId()) {
|
||||
if (session && fields.userId == session->userId().bare) {
|
||||
return MTP_inputUserSelf();
|
||||
} else if (fields.userId) {
|
||||
return MTP_inputUser(MTP_int(fields.userId), MTP_long(fields.accessHash));
|
||||
|
||||
@@ -131,13 +131,13 @@ bool MentionUsersLoaded(
|
||||
for (const auto &entity : entities.v) {
|
||||
auto type = entity.type();
|
||||
if (type == mtpc_messageEntityMentionName) {
|
||||
if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id().v)) {
|
||||
if (!session->data().userLoaded(entity.c_messageEntityMentionName().vuser_id())) {
|
||||
return false;
|
||||
}
|
||||
} else if (type == mtpc_inputMessageEntityMentionName) {
|
||||
auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id();
|
||||
if (inputUser.type() == mtpc_inputUser) {
|
||||
if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id().v)) {
|
||||
if (!session->data().userLoaded(inputUser.c_inputUser().vuser_id())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ DataIsLoadedResult AllDataLoadedForMessage(
|
||||
}
|
||||
}
|
||||
if (const auto viaBotId = message.vvia_bot_id()) {
|
||||
if (!session->data().userLoaded(viaBotId->v)) {
|
||||
if (!session->data().userLoaded(*viaBotId)) {
|
||||
return DataIsLoadedResult::NotLoaded;
|
||||
}
|
||||
}
|
||||
@@ -181,19 +181,19 @@ DataIsLoadedResult AllDataLoadedForMessage(
|
||||
}
|
||||
return message.vaction().match(
|
||||
[&](const MTPDmessageActionChatAddUser &action) {
|
||||
for (const MTPint &userId : action.vusers().v) {
|
||||
if (!session->data().userLoaded(userId.v)) {
|
||||
for (const auto &userId : action.vusers().v) {
|
||||
if (!session->data().userLoaded(userId)) {
|
||||
return DataIsLoadedResult::NotLoaded;
|
||||
}
|
||||
}
|
||||
return DataIsLoadedResult::Ok;
|
||||
}, [&](const MTPDmessageActionChatJoinedByLink &action) {
|
||||
if (!session->data().userLoaded(action.vinviter_id().v)) {
|
||||
if (!session->data().userLoaded(action.vinviter_id())) {
|
||||
return DataIsLoadedResult::NotLoaded;
|
||||
}
|
||||
return DataIsLoadedResult::Ok;
|
||||
}, [&](const MTPDmessageActionChatDeleteUser &action) {
|
||||
if (!session->data().userLoaded(action.vuser_id().v)) {
|
||||
if (!session->data().userLoaded(action.vuser_id())) {
|
||||
return DataIsLoadedResult::NotLoaded;
|
||||
}
|
||||
return DataIsLoadedResult::Ok;
|
||||
@@ -1020,7 +1020,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
| MTPDmessage::Flag::f_from_id;
|
||||
const auto peerUserId = d.is_out()
|
||||
? d.vuser_id()
|
||||
: MTP_int(_session->userId());
|
||||
: MTP_int(_session->userId().bare); // #TODO ids
|
||||
_session->data().addNewMessage(
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
@@ -1309,8 +1309,8 @@ void Updates::applyUpdates(
|
||||
const auto viaBotId = d.vvia_bot_id();
|
||||
const auto entities = d.ventities();
|
||||
const auto fwd = d.vfwd_from();
|
||||
if (!session().data().userLoaded(d.vuser_id().v)
|
||||
|| (viaBotId && !session().data().userLoaded(viaBotId->v))
|
||||
if (!session().data().userLoaded(d.vuser_id())
|
||||
|| (viaBotId && !session().data().userLoaded(*viaBotId))
|
||||
|| (entities && !MentionUsersLoaded(&session(), *entities))
|
||||
|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
|
||||
MTP_LOG(0, ("getDifference "
|
||||
@@ -1326,14 +1326,14 @@ void Updates::applyUpdates(
|
||||
|
||||
case mtpc_updateShortChatMessage: {
|
||||
auto &d = updates.c_updateShortChatMessage();
|
||||
const auto noFrom = !session().data().userLoaded(d.vfrom_id().v);
|
||||
const auto chat = session().data().chatLoaded(d.vchat_id().v);
|
||||
const auto noFrom = !session().data().userLoaded(d.vfrom_id());
|
||||
const auto chat = session().data().chatLoaded(d.vchat_id());
|
||||
const auto viaBotId = d.vvia_bot_id();
|
||||
const auto entities = d.ventities();
|
||||
const auto fwd = d.vfwd_from();
|
||||
if (!chat
|
||||
|| noFrom
|
||||
|| (viaBotId && !session().data().userLoaded(viaBotId->v))
|
||||
|| (viaBotId && !session().data().userLoaded(*viaBotId))
|
||||
|| (entities && !MentionUsersLoaded(&session(), *entities))
|
||||
|| (fwd && !ForwardedInfoDataLoaded(&session(), *fwd))) {
|
||||
MTP_LOG(0, ("getDifference "
|
||||
@@ -1491,7 +1491,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateChannelReadMessagesContents: {
|
||||
auto &d = update.c_updateChannelReadMessagesContents();
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id().v);
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
if (!channel) {
|
||||
if (!_byMinChannelTimer.isActive()) {
|
||||
// getDifference after timeout.
|
||||
@@ -1537,7 +1537,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updatePinnedChannelMessages: {
|
||||
auto &d = update.c_updatePinnedChannelMessages();
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id().v);
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
@@ -1623,7 +1623,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateDeleteChannelMessages: {
|
||||
auto &d = update.c_updateDeleteChannelMessages();
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id().v);
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
@@ -1662,7 +1662,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
session().data().processWebpage(d.vwebpage());
|
||||
session().data().sendWebPageGamePollNotifications();
|
||||
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id().v);
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
return;
|
||||
@@ -1683,27 +1683,25 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
handleSendActionUpdate(
|
||||
peerFromUser(d.vuser_id()),
|
||||
0,
|
||||
d.vuser_id().v,
|
||||
peerFromUser(d.vuser_id()),
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChatUserTyping: {
|
||||
auto &d = update.c_updateChatUserTyping();
|
||||
const auto fromId = peerFromMTP(d.vfrom_id());
|
||||
handleSendActionUpdate(
|
||||
peerFromChat(d.vchat_id()),
|
||||
0,
|
||||
fromId,
|
||||
peerFromMTP(d.vfrom_id()),
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannelUserTyping: {
|
||||
const auto &d = update.c_updateChannelUserTyping();
|
||||
const auto fromId = peerFromMTP(d.vfrom_id());
|
||||
handleSendActionUpdate(
|
||||
peerFromChannel(d.vchannel_id()),
|
||||
d.vtop_msg_id().value_or_empty(),
|
||||
fromId,
|
||||
peerFromMTP(d.vfrom_id()),
|
||||
d.vaction());
|
||||
} break;
|
||||
|
||||
@@ -1729,7 +1727,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateUserStatus: {
|
||||
auto &d = update.c_updateUserStatus();
|
||||
if (auto user = session().data().userLoaded(d.vuser_id().v)) {
|
||||
if (auto user = session().data().userLoaded(d.vuser_id())) {
|
||||
switch (d.vstatus().type()) {
|
||||
case mtpc_userStatusEmpty: user->onlineTill = 0; break;
|
||||
case mtpc_userStatusRecently:
|
||||
@@ -1746,7 +1744,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
user,
|
||||
Data::PeerUpdate::Flag::OnlineStatus);
|
||||
}
|
||||
if (d.vuser_id().v == session().userId()) {
|
||||
if (UserId(d.vuser_id()) == session().userId()) {
|
||||
if (d.vstatus().type() == mtpc_userStatusOffline
|
||||
|| d.vstatus().type() == mtpc_userStatusEmpty) {
|
||||
updateOnline(true);
|
||||
@@ -1763,7 +1761,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateUserName: {
|
||||
auto &d = update.c_updateUserName();
|
||||
if (auto user = session().data().userLoaded(d.vuser_id().v)) {
|
||||
if (auto user = session().data().userLoaded(d.vuser_id())) {
|
||||
if (!user->isContact()) {
|
||||
user->setName(
|
||||
TextUtilities::SingleLine(qs(d.vfirst_name())),
|
||||
@@ -1782,7 +1780,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateUserPhoto: {
|
||||
auto &d = update.c_updateUserPhoto();
|
||||
if (auto user = session().data().userLoaded(d.vuser_id().v)) {
|
||||
if (auto user = session().data().userLoaded(d.vuser_id())) {
|
||||
user->setPhoto(d.vphoto());
|
||||
user->loadUserpic();
|
||||
// After that update we don't have enough information to
|
||||
@@ -1793,11 +1791,11 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
//
|
||||
//if (mtpIsTrue(d.vprevious()) || !user->userpicPhotoId()) {
|
||||
session().storage().remove(Storage::UserPhotosRemoveAfter(
|
||||
user->bareId(),
|
||||
peerToUser(user->id),
|
||||
user->userpicPhotoId()));
|
||||
//} else {
|
||||
// session().storage().add(Storage::UserPhotosAddNew(
|
||||
// user->bareId(),
|
||||
// peerToUser(user->id),
|
||||
// user->userpicPhotoId()));
|
||||
//}
|
||||
}
|
||||
@@ -1831,7 +1829,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateUserPhone: {
|
||||
const auto &d = update.c_updateUserPhone();
|
||||
if (const auto user = session().data().userLoaded(d.vuser_id().v)) {
|
||||
if (const auto user = session().data().userLoaded(d.vuser_id())) {
|
||||
const auto newPhone = qs(d.vphone());
|
||||
if (newPhone != user->phone()) {
|
||||
user->setPhone(newPhone);
|
||||
@@ -1914,8 +1912,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
auto &d = update.c_updatePrivacy();
|
||||
const auto allChatsLoaded = [&](const MTPVector<MTPint> &ids) {
|
||||
for (const auto &chatId : ids.v) {
|
||||
if (!session().data().chatLoaded(chatId.v)
|
||||
&& !session().data().channelLoaded(chatId.v)) {
|
||||
if (!session().data().chatLoaded(chatId)
|
||||
&& !session().data().channelLoaded(chatId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1999,7 +1997,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
DEBUG_LOG(("API Error: "
|
||||
"pinned chat not loaded for peer %1, folder: %2"
|
||||
).arg(id
|
||||
).arg(id.value
|
||||
).arg(folderId
|
||||
));
|
||||
return false;
|
||||
@@ -2027,7 +2025,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateChannel: {
|
||||
auto &d = update.c_updateChannel();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
channel->inviter = UserId(0);
|
||||
if (channel->amIn()) {
|
||||
if (channel->isMegagroup()
|
||||
@@ -2049,7 +2047,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateChannelTooLong: {
|
||||
const auto &d = update.c_updateChannelTooLong();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
const auto pts = d.vpts();
|
||||
if (!pts || channel->pts() < pts->v) {
|
||||
getChannelDifference(channel);
|
||||
@@ -2108,7 +2106,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateChannelAvailableMessages: {
|
||||
auto &d = update.c_updateChannelAvailableMessages();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id().v)) {
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
channel->setAvailableMinId(d.vavailable_min_id().v);
|
||||
if (const auto history = session().data().historyLoaded(channel)) {
|
||||
history->clearUpTill(d.vavailable_min_id().v);
|
||||
|
||||
@@ -692,7 +692,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
if (inRepliesContext) {
|
||||
if (const auto rootId = item->replyToTop()) {
|
||||
const auto root = item->history()->owner().message(
|
||||
channel->bareId(),
|
||||
peerToChannel(channel->id),
|
||||
rootId);
|
||||
const auto sender = root
|
||||
? root->discussionPostOriginalSender()
|
||||
@@ -715,7 +715,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
const auto base = linkChannel->hasUsername()
|
||||
? linkChannel->username
|
||||
: "c/" + QString::number(linkChannel->bareId());
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto query = base
|
||||
+ '/'
|
||||
+ QString::number(linkItemId)
|
||||
@@ -776,7 +776,7 @@ void ApiWrap::requestContacts() {
|
||||
for (const auto &contact : d.vcontacts().v) {
|
||||
if (contact.type() != mtpc_contact) continue;
|
||||
|
||||
const auto userId = contact.c_contact().vuser_id().v;
|
||||
const auto userId = UserId(contact.c_contact().vuser_id());
|
||||
if (userId == _session->userId()) {
|
||||
_session->user()->setIsContact(true);
|
||||
}
|
||||
@@ -1522,13 +1522,20 @@ void ApiWrap::applyLastParticipantsList(
|
||||
|
||||
auto botStatus = channel->mgInfo->botStatus;
|
||||
const auto emptyAdminRights = MTP_chatAdminRights(MTP_flags(0));
|
||||
const auto emptyRestrictedRights = MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0));
|
||||
for (const auto &p : list) {
|
||||
const auto userId = p.match([](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
const auto participantId = p.match([](
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const MTPDchannelParticipantLeft &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const auto &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
});
|
||||
if (!participantId) {
|
||||
continue;
|
||||
}
|
||||
const auto participant = _session->data().peer(participantId);
|
||||
const auto user = participant->asUser();
|
||||
const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin)
|
||||
? p.c_channelParticipantAdmin().is_can_edit()
|
||||
: (p.type() == mtpc_channelParticipantCreator)
|
||||
@@ -1541,28 +1548,27 @@ void ApiWrap::applyLastParticipantsList(
|
||||
: emptyAdminRights;
|
||||
const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned)
|
||||
? p.c_channelParticipantBanned().vbanned_rights()
|
||||
: emptyRestrictedRights;
|
||||
if (!userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto user = _session->data().user(userId);
|
||||
: ChannelData::EmptyRestrictedRights(participant);
|
||||
if (p.type() == mtpc_channelParticipantCreator) {
|
||||
Assert(user != nullptr);
|
||||
const auto &creator = p.c_channelParticipantCreator();
|
||||
const auto rank = qs(creator.vrank().value_or_empty());
|
||||
channel->mgInfo->creator = user;
|
||||
channel->mgInfo->creatorRank = rank;
|
||||
if (!channel->mgInfo->admins.empty()) {
|
||||
Data::ChannelAdminChanges(channel).add(userId, rank);
|
||||
Data::ChannelAdminChanges(channel).add(
|
||||
peerToUser(participantId),
|
||||
rank);
|
||||
}
|
||||
}
|
||||
if (!base::contains(channel->mgInfo->lastParticipants, user)) {
|
||||
if (user
|
||||
&& !base::contains(channel->mgInfo->lastParticipants, user)) {
|
||||
channel->mgInfo->lastParticipants.push_back(user);
|
||||
if (adminRights.c_chatAdminRights().vflags().v) {
|
||||
channel->mgInfo->lastAdmins.emplace(
|
||||
user,
|
||||
MegagroupInfo::Admin{ adminRights, adminCanEdit });
|
||||
} else if (restrictedRights.c_chatBannedRights().vflags().v != 0) {
|
||||
} else if (Data::ChatBannedRightsFlags(restrictedRights) != 0) {
|
||||
channel->mgInfo->lastRestricted.emplace(
|
||||
user,
|
||||
MegagroupInfo::Restricted{ restrictedRights });
|
||||
@@ -1607,22 +1613,29 @@ void ApiWrap::applyBotsList(
|
||||
auto botStatus = channel->mgInfo->botStatus;
|
||||
auto keyboardBotFound = !history || !history->lastKeyboardFrom;
|
||||
for (const auto &p : list) {
|
||||
const auto userId = p.match([](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
const auto participantId = p.match([](
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const MTPDchannelParticipantLeft &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const auto &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
});
|
||||
if (!userId) {
|
||||
if (!participantId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto user = _session->data().user(userId);
|
||||
if (user->isBot()) {
|
||||
const auto participant = _session->data().peer(participantId);
|
||||
const auto user = participant->asUser();
|
||||
if (user && user->isBot()) {
|
||||
channel->mgInfo->bots.insert(user);
|
||||
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
|
||||
if (!user->botInfo->inited) {
|
||||
needBotsInfos = true;
|
||||
}
|
||||
}
|
||||
if (!keyboardBotFound && user->id == history->lastKeyboardFrom) {
|
||||
if (!keyboardBotFound
|
||||
&& participant->id == history->lastKeyboardFrom) {
|
||||
keyboardBotFound = true;
|
||||
}
|
||||
}
|
||||
@@ -1657,7 +1670,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
_selfParticipantRequests.emplace(channel);
|
||||
request(MTPchannels_GetParticipant(
|
||||
channel->inputChannel,
|
||||
MTP_inputUserSelf()
|
||||
MTP_inputPeerSelf()
|
||||
)).done([=](const MTPchannels_ChannelParticipant &result) {
|
||||
_selfParticipantRequests.erase(channel);
|
||||
result.match([&](const MTPDchannels_channelParticipant &data) {
|
||||
@@ -1698,11 +1711,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<UserData*> user) {
|
||||
not_null<PeerData*> participant) {
|
||||
Expects(participant->isUser());
|
||||
|
||||
request(MTPmessages_DeleteChatUser(
|
||||
MTP_flags(0),
|
||||
chat->inputChat,
|
||||
user->inputUser
|
||||
participant->asUser()->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).send();
|
||||
@@ -1710,21 +1725,21 @@ void ApiWrap::kickParticipant(
|
||||
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights ¤tRights) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
const auto kick = KickRequest(channel, participant);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
const auto rights = ChannelData::KickedRestrictedRights();
|
||||
const auto rights = ChannelData::KickedRestrictedRights(participant);
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
participant->input,
|
||||
rights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
_kickRequests.remove(KickRequest(channel, participant));
|
||||
channel->applyEditBanned(participant, currentRights, rights);
|
||||
}).fail([this, kick](const MTP::Error &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
@@ -1734,20 +1749,20 @@ void ApiWrap::kickParticipant(
|
||||
|
||||
void ApiWrap::unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
not_null<PeerData*> participant) {
|
||||
const auto kick = KickRequest(channel, participant);
|
||||
if (_kickRequests.contains(kick)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
MTP_chatBannedRights(MTP_flags(0), MTP_int(0))
|
||||
participant->input,
|
||||
ChannelData::EmptyRestrictedRights(participant)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
_kickRequests.remove(KickRequest(channel, participant));
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
@@ -2252,7 +2267,7 @@ void ApiWrap::updatePrivacyLastSeens(const QVector<MTPPrivacyRule> &rules) {
|
||||
for (const auto &item : result.v) {
|
||||
Assert(item.type() == mtpc_contactStatus);
|
||||
auto &data = item.c_contactStatus();
|
||||
if (auto user = _session->data().userLoaded(data.vuser_id().v)) {
|
||||
if (auto user = _session->data().userLoaded(data.vuser_id())) {
|
||||
auto oldOnlineTill = user->onlineTill;
|
||||
auto newOnlineTill = OnlineTillFromStatus(data.vstatus(), oldOnlineTill);
|
||||
if (oldOnlineTill != newOnlineTill) {
|
||||
@@ -3183,12 +3198,20 @@ void ApiWrap::refreshChannelAdmins(
|
||||
const QVector<MTPChannelParticipant> &participants) {
|
||||
Data::ChannelAdminChanges changes(channel);
|
||||
for (const auto &p : participants) {
|
||||
const auto userId = p.match([](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
const auto participantId = p.match([](
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const MTPDchannelParticipantLeft &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const auto &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
});
|
||||
const auto userId = peerToUser(participantId);
|
||||
p.match([&](const MTPDchannelParticipantAdmin &data) {
|
||||
Assert(peerIsUser(participantId));
|
||||
changes.add(userId, qs(data.vrank().value_or_empty()));
|
||||
}, [&](const MTPDchannelParticipantCreator &data) {
|
||||
Assert(peerIsUser(participantId));
|
||||
const auto rank = qs(data.vrank().value_or_empty());
|
||||
if (const auto info = channel->mgInfo.get()) {
|
||||
info->creator = channel->owner().userLoaded(userId);
|
||||
@@ -3196,7 +3219,9 @@ void ApiWrap::refreshChannelAdmins(
|
||||
}
|
||||
changes.add(userId, rank);
|
||||
}, [&](const auto &data) {
|
||||
changes.remove(userId);
|
||||
if (userId) {
|
||||
changes.remove(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3541,7 +3566,7 @@ void ApiWrap::userPhotosDone(
|
||||
}
|
||||
}
|
||||
_session->storage().add(Storage::UserPhotosAddSlice(
|
||||
user->id,
|
||||
peerToUser(user->id),
|
||||
std::move(photoIds),
|
||||
fullCount
|
||||
));
|
||||
@@ -3784,7 +3809,7 @@ void ApiWrap::sendSharedContact(
|
||||
MTP_string(firstName),
|
||||
MTP_string(lastName),
|
||||
MTP_string(vcard),
|
||||
MTP_int(userId)),
|
||||
MTP_int(userId.bare)), // #TODO ids
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(views),
|
||||
@@ -4588,7 +4613,7 @@ void ApiWrap::clearPeerPhoto(not_null<PhotoData*> photo) {
|
||||
MTP_vector<MTPInputPhoto>(1, photo->mtpInput())
|
||||
)).send();
|
||||
_session->storage().remove(Storage::UserPhotosRemoveOne(
|
||||
self->bareId(),
|
||||
peerToUser(self->id),
|
||||
photo->id));
|
||||
}
|
||||
}
|
||||
@@ -4765,11 +4790,11 @@ auto ApiWrap::parsePrivacy(const QVector<MTPPrivacyRule> &rules)
|
||||
}, [&](const MTPDprivacyValueAllowChatParticipants &data) {
|
||||
const auto &chats = data.vchats().v;
|
||||
always.reserve(always.size() + chats.size());
|
||||
for (const auto chatId : chats) {
|
||||
const auto chat = _session->data().chatLoaded(chatId.v);
|
||||
for (const auto &chatId : chats) {
|
||||
const auto chat = _session->data().chatLoaded(chatId);
|
||||
const auto peer = chat
|
||||
? static_cast<PeerData*>(chat)
|
||||
: _session->data().channelLoaded(chatId.v);
|
||||
: _session->data().channelLoaded(chatId);
|
||||
if (peer
|
||||
&& !base::contains(never, peer)
|
||||
&& !base::contains(always, peer)) {
|
||||
@@ -4793,11 +4818,11 @@ auto ApiWrap::parsePrivacy(const QVector<MTPPrivacyRule> &rules)
|
||||
}, [&](const MTPDprivacyValueDisallowChatParticipants &data) {
|
||||
const auto &chats = data.vchats().v;
|
||||
never.reserve(never.size() + chats.size());
|
||||
for (const auto chatId : chats) {
|
||||
const auto chat = _session->data().chatLoaded(chatId.v);
|
||||
for (const auto &chatId : chats) {
|
||||
const auto chat = _session->data().chatLoaded(chatId);
|
||||
const auto peer = chat
|
||||
? static_cast<PeerData*>(chat)
|
||||
: _session->data().channelLoaded(chatId.v);
|
||||
: _session->data().channelLoaded(chatId);
|
||||
if (peer
|
||||
&& !base::contains(always, peer)
|
||||
&& !base::contains(never, peer)) {
|
||||
|
||||
@@ -76,6 +76,15 @@ inline QString ToString(uint64 value) {
|
||||
return QString::number(value);
|
||||
}
|
||||
|
||||
template <uchar Shift>
|
||||
inline QString ToString(ChatIdType<Shift> value) {
|
||||
return QString::number(value.bare);
|
||||
}
|
||||
|
||||
inline QString ToString(PeerId value) {
|
||||
return QString::number(value.value);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
template <
|
||||
@@ -249,14 +258,16 @@ public:
|
||||
void markMediaRead(not_null<HistoryItem*> item);
|
||||
|
||||
void requestSelfParticipant(not_null<ChannelData*> channel);
|
||||
void kickParticipant(not_null<ChatData*> chat, not_null<UserData*> user);
|
||||
void kickParticipant(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<PeerData*> participant);
|
||||
void kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights ¤tRights);
|
||||
void unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
not_null<PeerData*> participant);
|
||||
void deleteAllFromUser(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> from);
|
||||
@@ -657,7 +668,7 @@ private:
|
||||
|
||||
using KickRequest = std::pair<
|
||||
not_null<ChannelData*>,
|
||||
not_null<UserData*>>;
|
||||
not_null<PeerData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
base::flat_set<not_null<ChannelData*>> _selfParticipantRequests;
|
||||
|
||||
@@ -94,7 +94,7 @@ void ChatCreateDone(
|
||||
}
|
||||
| [&](auto chats) {
|
||||
return navigation->session().data().chat(
|
||||
chats->front().c_chat().vid().v);
|
||||
chats->front().c_chat().vid());
|
||||
}
|
||||
| [&](not_null<ChatData*> chat) {
|
||||
if (!image.isNull()) {
|
||||
@@ -268,7 +268,7 @@ AddContactBox::AddContactBox(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
tr::lng_contact_phone(),
|
||||
ExtractPhonePrefix(session->user()->phone()),
|
||||
Ui::ExtractPhonePrefix(session->user()->phone()),
|
||||
phone)
|
||||
, _invertOrder(langFirstNameGoesSecond()) {
|
||||
if (!phone.isEmpty()) {
|
||||
@@ -401,7 +401,7 @@ void AddContactBox::save() {
|
||||
const auto extractUser = [&](const MTPImportedContact &data) {
|
||||
return data.match([&](const MTPDimportedContact &data) {
|
||||
return (data.vclient_id().v == _contactId)
|
||||
? _session->data().userLoaded(data.vuser_id().v)
|
||||
? _session->data().userLoaded(data.vuser_id())
|
||||
: nullptr;
|
||||
});
|
||||
};
|
||||
@@ -687,7 +687,7 @@ void GroupInfoBox::createChannel(const QString &title, const QString &descriptio
|
||||
}
|
||||
| [&](auto chats) {
|
||||
return _navigation->session().data().channel(
|
||||
chats->front().c_channel().vid().v);
|
||||
chats->front().c_channel().vid());
|
||||
}
|
||||
| [&](not_null<ChannelData*> channel) {
|
||||
auto image = _photo->takeResultImage();
|
||||
|
||||
@@ -295,7 +295,7 @@ AdminLog::OwnedItem GenerateTextItem(
|
||||
| (out ? Flag::f_out : Flag(0));
|
||||
const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item;
|
||||
const auto replyTo = 0;
|
||||
const auto viaBotId = 0;
|
||||
const auto viaBotId = UserId(0);
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
flags,
|
||||
@@ -402,14 +402,12 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
, _controller(controller)
|
||||
, _text1(GenerateTextItem(
|
||||
delegate(),
|
||||
_controller->session().data().history(
|
||||
peerFromUser(PeerData::kServiceNotificationsId)),
|
||||
_controller->session().data().history(PeerData::kServiceNotificationsId),
|
||||
tr::lng_background_text1(tr::now),
|
||||
false))
|
||||
, _text2(GenerateTextItem(
|
||||
delegate(),
|
||||
_controller->session().data().history(
|
||||
peerFromUser(PeerData::kServiceNotificationsId)),
|
||||
_controller->session().data().history(PeerData::kServiceNotificationsId),
|
||||
tr::lng_background_text2(tr::now),
|
||||
true))
|
||||
, _paper(paper)
|
||||
|
||||
@@ -151,7 +151,7 @@ void ChangePhoneBox::EnterPhone::prepare() {
|
||||
this,
|
||||
st::defaultInputField,
|
||||
tr::lng_change_phone_new_title(),
|
||||
ExtractPhonePrefix(_session->user()->phone()),
|
||||
Ui::ExtractPhonePrefix(_session->user()->phone()),
|
||||
phoneValue);
|
||||
|
||||
_phone->resize(st::boxWidth - 2 * st::boxPadding.left(), _phone->height());
|
||||
|
||||
@@ -304,9 +304,10 @@ void ConfirmBox::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_lastMousePos = e->globalPos();
|
||||
updateHover();
|
||||
if (const auto activated = ClickHandler::unpressed()) {
|
||||
const auto guard = window();
|
||||
Ui::hideLayer();
|
||||
ActivateClickHandler(guard, activated, e->button());
|
||||
ActivateClickHandler(window(), activated, e->button());
|
||||
crl::on_main(this, [=] {
|
||||
closeBox();
|
||||
});
|
||||
return;
|
||||
}
|
||||
BoxContent::mouseReleaseEvent(e);
|
||||
@@ -900,7 +901,7 @@ void DeleteMessagesBox::deleteAndClear() {
|
||||
_moderateInChannel->session().api().kickParticipant(
|
||||
_moderateInChannel,
|
||||
_moderateFrom,
|
||||
MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
|
||||
ChannelData::EmptyRestrictedRights(_moderateFrom));
|
||||
}
|
||||
if (_reportSpam->checked()) {
|
||||
_moderateInChannel->session().api().request(
|
||||
|
||||
@@ -71,14 +71,6 @@ void ShowPhoneBannedError(const QString &phone) {
|
||||
[=] { SendToBannedHelp(phone); close(); }));
|
||||
}
|
||||
|
||||
QString ExtractPhonePrefix(const QString &phone) {
|
||||
const auto pattern = phoneNumberParse(phone);
|
||||
if (!pattern.isEmpty()) {
|
||||
return phone.mid(0, pattern[0]);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
SentCodeField::SentCodeField(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
|
||||
@@ -22,7 +22,6 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
void ShowPhoneBannedError(const QString &phone);
|
||||
[[nodiscard]] QString ExtractPhonePrefix(const QString &phone);
|
||||
|
||||
class SentCodeField : public Ui::InputField {
|
||||
public:
|
||||
|
||||
@@ -182,11 +182,11 @@ QVector<MTPInputPrivacyRule> EditPrivacyBox::collectResult() {
|
||||
return result;
|
||||
};
|
||||
const auto collectInputChats = [](const auto &peers) {
|
||||
auto result = QVector<MTPint>();
|
||||
auto result = QVector<MTPint>(); // #TODO ids
|
||||
result.reserve(peers.size());
|
||||
for (const auto peer : peers) {
|
||||
if (!peer->isUser()) {
|
||||
result.push_back(MTP_int(peer->bareId()));
|
||||
result.push_back(peerToBareMTPInt(peer->id));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -119,7 +119,7 @@ private:
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 TypeId(Flag flag) {
|
||||
return PeerIdFakeShift | static_cast<uint64>(flag);
|
||||
return PeerId(FakeChatId(static_cast<BareId>(flag))).value;
|
||||
}
|
||||
|
||||
TypeRow::TypeRow(Flag flag) : PeerListRow(TypeId(flag)) {
|
||||
|
||||
@@ -361,7 +361,11 @@ void PasscodeBox::closeReplacedBy() {
|
||||
}
|
||||
|
||||
void PasscodeBox::setPasswordFail(const MTP::Error &error) {
|
||||
if (MTP::IsFloodError(error)) {
|
||||
setPasswordFail(error.type());
|
||||
}
|
||||
|
||||
void PasscodeBox::setPasswordFail(const QString &type) {
|
||||
if (MTP::IsFloodError(type)) {
|
||||
closeReplacedBy();
|
||||
_setRequest = 0;
|
||||
|
||||
@@ -378,20 +382,19 @@ void PasscodeBox::setPasswordFail(const MTP::Error &error) {
|
||||
|
||||
closeReplacedBy();
|
||||
_setRequest = 0;
|
||||
const auto &err = error.type();
|
||||
if (err == qstr("PASSWORD_HASH_INVALID")
|
||||
|| err == qstr("SRP_PASSWORD_CHANGED")) {
|
||||
if (type == qstr("PASSWORD_HASH_INVALID")
|
||||
|| type == qstr("SRP_PASSWORD_CHANGED")) {
|
||||
if (_oldPasscode->isHidden()) {
|
||||
_passwordReloadNeeded.fire({});
|
||||
closeBox();
|
||||
} else {
|
||||
badOldPasscode();
|
||||
}
|
||||
} else if (err == qstr("SRP_ID_INVALID")) {
|
||||
} else if (type == qstr("SRP_ID_INVALID")) {
|
||||
handleSrpIdInvalid();
|
||||
//} else if (err == qstr("NEW_PASSWORD_BAD")) {
|
||||
//} else if (err == qstr("NEW_SALT_INVALID")) {
|
||||
} else if (err == qstr("EMAIL_INVALID")) {
|
||||
//} else if (type == qstr("NEW_PASSWORD_BAD")) {
|
||||
//} else if (type == qstr("NEW_SALT_INVALID")) {
|
||||
} else if (type == qstr("EMAIL_INVALID")) {
|
||||
_emailError = tr::lng_cloud_password_bad_email(tr::now);
|
||||
_recoverEmail->setFocus();
|
||||
_recoverEmail->showError();
|
||||
@@ -682,12 +685,15 @@ void PasscodeBox::serverError() {
|
||||
}
|
||||
|
||||
bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
if (MTP::IsFloodError(error)
|
||||
return handleCustomCheckError(error.type());
|
||||
}
|
||||
|
||||
bool PasscodeBox::handleCustomCheckError(const QString &type) {
|
||||
if (MTP::IsFloodError(type)
|
||||
|| type == qstr("PASSWORD_HASH_INVALID")
|
||||
|| type == qstr("SRP_PASSWORD_CHANGED")
|
||||
|| type == qstr("SRP_ID_INVALID")) {
|
||||
setPasswordFail(error);
|
||||
setPasswordFail(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
rpl::producer<> clearUnconfirmedPassword() const;
|
||||
|
||||
bool handleCustomCheckError(const MTP::Error &error);
|
||||
bool handleCustomCheckError(const QString &type);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -82,6 +83,7 @@ private:
|
||||
|
||||
void setPasswordDone(const QByteArray &newPasswordBytes);
|
||||
void setPasswordFail(const MTP::Error &error);
|
||||
void setPasswordFail(const QString &type);
|
||||
void setPasswordFail(
|
||||
const QByteArray &newPasswordBytes,
|
||||
const QString &email,
|
||||
|
||||
@@ -86,8 +86,8 @@ void PeerListBox::createMultiSelect() {
|
||||
if (_controller->handleDeselectForeignRow(itemId)) {
|
||||
return;
|
||||
}
|
||||
if (const auto peer = _controller->session().data().peerLoaded(itemId)) {
|
||||
if (const auto row = peerListFindRow(peer->id)) {
|
||||
if (const auto peer = _controller->session().data().peerLoaded(PeerId(itemId))) {
|
||||
if (const auto row = peerListFindRow(itemId)) {
|
||||
content()->changeCheckState(row, false, anim::type::normal);
|
||||
update();
|
||||
}
|
||||
@@ -275,11 +275,11 @@ void PeerListController::search(const QString &query) {
|
||||
}
|
||||
|
||||
void PeerListController::peerListSearchAddRow(not_null<PeerData*> peer) {
|
||||
if (auto row = delegate()->peerListFindRow(peer->id)) {
|
||||
Assert(row->id() == row->peer()->id);
|
||||
if (auto row = delegate()->peerListFindRow(peer->id.value)) {
|
||||
Assert(row->id() == row->peer()->id.value);
|
||||
delegate()->peerListAppendFoundRow(row);
|
||||
} else if (auto row = createSearchRow(peer)) {
|
||||
Assert(row->id() == row->peer()->id);
|
||||
Assert(row->id() == row->peer()->id.value);
|
||||
delegate()->peerListAppendSearchRow(std::move(row));
|
||||
}
|
||||
}
|
||||
@@ -353,7 +353,7 @@ void PeerListBox::addSelectItem(
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->shortName();
|
||||
addSelectItem(
|
||||
peer->id,
|
||||
peer->id.value,
|
||||
text,
|
||||
PaintUserpicCallback(peer, respect),
|
||||
animated);
|
||||
@@ -420,7 +420,7 @@ auto PeerListBox::collectSelectedRows()
|
||||
result.reserve(items.size());
|
||||
for (const auto itemId : items) {
|
||||
if (!_controller->isForeignRow(itemId)) {
|
||||
result.push_back(_controller->session().data().peer(itemId));
|
||||
result.push_back(_controller->session().data().peer(PeerId(itemId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +428,7 @@ auto PeerListBox::collectSelectedRows()
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer, peer->id) {
|
||||
: PeerListRow(peer, peer->id.value) {
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id)
|
||||
@@ -800,7 +800,7 @@ void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
addToSearchIndex(row);
|
||||
}
|
||||
if (_controller->isRowSelected(row)) {
|
||||
Assert(row->special() || row->id() == row->peer()->id);
|
||||
Assert(row->special() || row->id() == row->peer()->id.value);
|
||||
changeCheckState(row, true, anim::type::instant);
|
||||
}
|
||||
}
|
||||
@@ -1643,7 +1643,7 @@ void PeerListContent::restoreState(
|
||||
auto searchWords = TextUtilities::PrepareSearchWords(query);
|
||||
setSearchQuery(query, searchWords.join(' '));
|
||||
for (auto peer : state->filterResults) {
|
||||
if (auto existingRow = findRow(peer->id)) {
|
||||
if (auto existingRow = findRow(peer->id.value)) {
|
||||
_filterResults.push_back(existingRow);
|
||||
} else if (auto row = _controller->createSearchRow(peer)) {
|
||||
appendSearchRow(std::move(row));
|
||||
|
||||
@@ -344,7 +344,7 @@ std::unique_ptr<PeerListRow> ChatsListBoxController::createSearchRow(not_null<Pe
|
||||
}
|
||||
|
||||
bool ChatsListBoxController::appendRow(not_null<History*> history) {
|
||||
if (auto row = delegate()->peerListFindRow(history->peer->id)) {
|
||||
if (auto row = delegate()->peerListFindRow(history->peer->id.value)) {
|
||||
updateRowHook(static_cast<Row*>(row));
|
||||
return false;
|
||||
}
|
||||
@@ -426,7 +426,7 @@ void ContactsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
bool ContactsBoxController::appendRow(not_null<UserData*> user) {
|
||||
if (auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
updateRowHook(row);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ auto PeerListsBox::collectSelectedRows()
|
||||
return false;
|
||||
}();
|
||||
if (!foreign) {
|
||||
result.push_back(session->data().peer(itemId));
|
||||
result.push_back(session->data().peer(PeerId(itemId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,10 +108,10 @@ void PeerListsBox::createMultiSelect() {
|
||||
}
|
||||
}
|
||||
const auto session = &firstController()->session();
|
||||
if (const auto peer = session->data().peerLoaded(itemId)) {
|
||||
if (const auto peer = session->data().peerLoaded(PeerId(itemId))) {
|
||||
const auto id = peer->id;
|
||||
for (const auto &list : _lists) {
|
||||
if (const auto row = list.delegate->peerListFindRow(id)) {
|
||||
if (const auto row = list.delegate->peerListFindRow(id.value)) {
|
||||
list.content->changeCheckState(
|
||||
row,
|
||||
false,
|
||||
@@ -385,7 +385,7 @@ void PeerListsBox::addSelectItem(
|
||||
not_null<PeerData*> peer,
|
||||
anim::type animated) {
|
||||
addSelectItem(
|
||||
peer->id,
|
||||
peer->id.value,
|
||||
peer->shortName(),
|
||||
PaintUserpicCallback(peer, false),
|
||||
animated);
|
||||
|
||||
@@ -421,6 +421,7 @@ void AddSpecialBoxController::rebuildChatRows(not_null<ChatData*> chat) {
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto row = delegate()->peerListRowAt(i);
|
||||
Assert(row->peer()->isUser());
|
||||
auto user = row->peer()->asUser();
|
||||
if (participants.contains(user)) {
|
||||
++i;
|
||||
@@ -467,8 +468,9 @@ void AddSpecialBoxController::loadMoreRows() {
|
||||
int availableCount,
|
||||
const QVector<MTPChannelParticipant> &list) {
|
||||
for (const auto &data : list) {
|
||||
if (const auto user = _additional.applyParticipant(data)) {
|
||||
appendRow(user);
|
||||
if (const auto participant = _additional.applyParticipant(
|
||||
data)) {
|
||||
appendRow(participant);
|
||||
}
|
||||
}
|
||||
if (const auto size = list.size()) {
|
||||
@@ -491,20 +493,25 @@ void AddSpecialBoxController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void AddSpecialBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
auto user = row->peer()->asUser();
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
switch (_role) {
|
||||
case Role::Admins: return showAdmin(user);
|
||||
case Role::Restricted: return showRestricted(user);
|
||||
case Role::Kicked: return kickUser(user);
|
||||
case Role::Admins:
|
||||
Assert(user != nullptr);
|
||||
return showAdmin(user);
|
||||
case Role::Restricted:
|
||||
Assert(user != nullptr);
|
||||
return showRestricted(user);
|
||||
case Role::Kicked: return kickUser(participant);
|
||||
}
|
||||
Unexpected("Role in AddSpecialBoxController::rowClicked()");
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
bool AddSpecialBoxController::checkInfoLoaded(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
Callback callback) {
|
||||
if (_additional.infoLoaded(user)) {
|
||||
if (_additional.infoLoaded(participant)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -512,16 +519,15 @@ bool AddSpecialBoxController::checkInfoLoaded(
|
||||
const auto channel = _peer->asChannel();
|
||||
_api.request(MTPchannels_GetParticipant(
|
||||
channel->inputChannel,
|
||||
user->inputUser
|
||||
participant->input
|
||||
)).done([=](const MTPchannels_ChannelParticipant &result) {
|
||||
Expects(result.type() == mtpc_channels_channelParticipant);
|
||||
|
||||
const auto &participant = result.c_channels_channelParticipant();
|
||||
channel->owner().processUsers(participant.vusers());
|
||||
_additional.applyParticipant(participant.vparticipant());
|
||||
result.match([&](const MTPDchannels_channelParticipant &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
_additional.applyParticipant(data.vparticipant());
|
||||
});
|
||||
callback();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_additional.setExternal(user);
|
||||
_additional.setExternal(participant);
|
||||
callback();
|
||||
}).send();
|
||||
return false;
|
||||
@@ -655,12 +661,12 @@ void AddSpecialBoxController::editAdminDone(
|
||||
using Flag = MTPDchannelParticipantCreator::Flag;
|
||||
_additional.applyParticipant(MTP_channelParticipantCreator(
|
||||
MTP_flags(rank.isEmpty() ? Flag(0) : Flag::f_rank),
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
rights,
|
||||
MTP_string(rank)));
|
||||
} else if (rights.c_chatAdminRights().vflags().v == 0) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
MTP_int(date)));
|
||||
} else {
|
||||
using Flag = MTPDchannelParticipantAdmin::Flag;
|
||||
@@ -668,11 +674,11 @@ void AddSpecialBoxController::editAdminDone(
|
||||
_additional.applyParticipant(MTP_channelParticipantAdmin(
|
||||
MTP_flags(Flag::f_can_edit
|
||||
| (rank.isEmpty() ? Flag(0) : Flag::f_rank)),
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
MTPint(), // inviter_id
|
||||
MTP_int(alreadyPromotedBy
|
||||
? alreadyPromotedBy->bareId()
|
||||
: user->session().userId()),
|
||||
peerToBareMTPInt(alreadyPromotedBy
|
||||
? alreadyPromotedBy->id
|
||||
: user->session().userPeerId()),
|
||||
MTP_int(date),
|
||||
rights,
|
||||
MTP_string(rank)));
|
||||
@@ -724,15 +730,13 @@ void AddSpecialBoxController::showRestricted(
|
||||
// Finally edit the restricted.
|
||||
const auto currentRights = restrictedRights
|
||||
? *restrictedRights
|
||||
: MTPChatBannedRights(MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0)));
|
||||
: ChannelData::EmptyRestrictedRights(user);
|
||||
auto box = Box<EditRestrictedBox>(
|
||||
_peer,
|
||||
user,
|
||||
_additional.adminRights(user).has_value(),
|
||||
currentRights);
|
||||
if (_additional.canRestrictUser(user)) {
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
const MTPChatBannedRights &newRights) {
|
||||
editRestrictedDone(user, newRights);
|
||||
@@ -749,50 +753,57 @@ void AddSpecialBoxController::showRestricted(
|
||||
}
|
||||
|
||||
void AddSpecialBoxController::editRestrictedDone(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights) {
|
||||
if (_editParticipantBox) {
|
||||
_editParticipantBox->closeBox();
|
||||
}
|
||||
|
||||
const auto date = base::unixtime::now(); // Incorrect, but ignored.
|
||||
if (rights.c_chatBannedRights().vflags().v == 0) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
MTP_int(user->bareId()),
|
||||
MTP_int(date)));
|
||||
if (Data::ChatBannedRightsFlags(rights) == 0) {
|
||||
if (const auto user = participant->asUser()) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
peerToBareMTPInt(user->id),
|
||||
MTP_int(date)));
|
||||
} else {
|
||||
_additional.setExternal(participant);
|
||||
}
|
||||
} else {
|
||||
const auto kicked = rights.c_chatBannedRights().is_view_messages();
|
||||
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
|
||||
const auto kicked = Data::ChatBannedRightsFlags(rights)
|
||||
& ChatRestriction::f_view_messages;
|
||||
const auto alreadyRestrictedBy = _additional.restrictedBy(
|
||||
participant);
|
||||
_additional.applyParticipant(MTP_channelParticipantBanned(
|
||||
MTP_flags(kicked
|
||||
? MTPDchannelParticipantBanned::Flag::f_left
|
||||
: MTPDchannelParticipantBanned::Flag(0)),
|
||||
MTP_int(user->bareId()),
|
||||
MTP_int(alreadyRestrictedBy
|
||||
? alreadyRestrictedBy->bareId()
|
||||
: user->session().userId()),
|
||||
peerToMTP(participant->id),
|
||||
peerToBareMTPInt(alreadyRestrictedBy
|
||||
? alreadyRestrictedBy->id
|
||||
: participant->session().userPeerId()),
|
||||
MTP_int(date),
|
||||
rights));
|
||||
}
|
||||
if (const auto callback = _bannedDoneCallback) {
|
||||
callback(user, rights);
|
||||
callback(participant, rights);
|
||||
}
|
||||
}
|
||||
|
||||
void AddSpecialBoxController::kickUser(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
bool sure) {
|
||||
if (!checkInfoLoaded(user, [=] { kickUser(user); })) {
|
||||
if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto kickUserSure = crl::guard(this, [=] {
|
||||
kickUser(user, true);
|
||||
kickUser(participant, true);
|
||||
});
|
||||
|
||||
// Check restrictions.
|
||||
if (_additional.adminRights(user).has_value()
|
||||
|| _additional.isCreator(user)) {
|
||||
const auto user = participant->asUser();
|
||||
if (user && (_additional.adminRights(user).has_value()
|
||||
|| (_additional.isCreator(user)))) {
|
||||
// The user is an admin or creator.
|
||||
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
|
||||
if (!sure) {
|
||||
@@ -818,42 +829,44 @@ void AddSpecialBoxController::kickUser(
|
||||
: tr::lng_profile_sure_kick_channel)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->name);
|
||||
participant->name);
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(text, kickUserSure),
|
||||
Ui::LayerOption::KeepOther);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto restrictedRights = _additional.restrictedRights(user);
|
||||
const auto restrictedRights = _additional.restrictedRights(participant);
|
||||
const auto currentRights = restrictedRights
|
||||
? *restrictedRights
|
||||
: MTPChatBannedRights(MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0)));
|
||||
: ChannelData::EmptyRestrictedRights(participant);
|
||||
|
||||
const auto done = crl::guard(this, [=](
|
||||
const MTPChatBannedRights &newRights) {
|
||||
editRestrictedDone(user, newRights);
|
||||
editRestrictedDone(participant, newRights);
|
||||
});
|
||||
const auto fail = crl::guard(this, [=] {
|
||||
_editBox = nullptr;
|
||||
});
|
||||
const auto callback = SaveRestrictedCallback(_peer, user, done, fail);
|
||||
callback(currentRights, ChannelData::KickedRestrictedRights());
|
||||
const auto callback = SaveRestrictedCallback(
|
||||
_peer,
|
||||
participant,
|
||||
done,
|
||||
fail);
|
||||
callback(currentRights, ChannelData::KickedRestrictedRights(participant));
|
||||
}
|
||||
|
||||
bool AddSpecialBoxController::appendRow(not_null<UserData*> user) {
|
||||
if (delegate()->peerListFindRow(user->id)
|
||||
|| (_excludeSelf && user->isSelf())) {
|
||||
bool AddSpecialBoxController::appendRow(not_null<PeerData*> participant) {
|
||||
if (delegate()->peerListFindRow(participant->id.value)
|
||||
|| (_excludeSelf && participant->isSelf())) {
|
||||
return false;
|
||||
}
|
||||
delegate()->peerListAppendRow(createRow(user));
|
||||
delegate()->peerListAppendRow(createRow(participant));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
|
||||
if (delegate()->peerListFindRow(user->id)) {
|
||||
if (delegate()->peerListFindRow(user->id.value)) {
|
||||
return false;
|
||||
}
|
||||
delegate()->peerListPrependRow(createRow(user));
|
||||
@@ -861,8 +874,8 @@ bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> AddSpecialBoxController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
return std::make_unique<PeerListRow>(user);
|
||||
not_null<PeerData*> participant) const {
|
||||
return std::make_unique<PeerListRow>(participant);
|
||||
}
|
||||
|
||||
AddSpecialBoxSearchController::AddSpecialBoxSearchController(
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
const MTPChatAdminRights &adminRights,
|
||||
const QString &rank)>;
|
||||
using BannedDoneCallback = Fn<void(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &bannedRights)>;
|
||||
AddSpecialBoxController(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
|
||||
private:
|
||||
template <typename Callback>
|
||||
bool checkInfoLoaded(not_null<UserData*> user, Callback callback);
|
||||
bool checkInfoLoaded(not_null<PeerData*> participant, Callback callback);
|
||||
|
||||
void prepareChatRows(not_null<ChatData*> chat);
|
||||
void rebuildChatRows(not_null<ChatData*> chat);
|
||||
@@ -113,12 +113,13 @@ private:
|
||||
const QString &rank);
|
||||
void showRestricted(not_null<UserData*> user, bool sure = false);
|
||||
void editRestrictedDone(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights);
|
||||
void kickUser(not_null<UserData*> user, bool sure = false);
|
||||
bool appendRow(not_null<UserData*> user);
|
||||
void kickUser(not_null<PeerData*> participant, bool sure = false);
|
||||
bool appendRow(not_null<PeerData*> participant);
|
||||
bool prependRow(not_null<UserData*> user);
|
||||
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
void subscribeToMigration();
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -31,7 +31,7 @@ constexpr auto kMaxUserFirstLastName = 64; // See also add_contact_box.
|
||||
QString UserPhone(not_null<UserData*> user) {
|
||||
const auto phone = user->phone();
|
||||
return phone.isEmpty()
|
||||
? user->owner().findContactPhone(user->bareId())
|
||||
? user->owner().findContactPhone(peerToUser(user->id))
|
||||
: phone;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ int Controller::contentWidth() const {
|
||||
|
||||
void Controller::prepare() {
|
||||
const auto appendRow = [&](not_null<PeerData*> chat) {
|
||||
if (delegate()->peerListFindRow(chat->id)) {
|
||||
if (delegate()->peerListFindRow(chat->id.value)) {
|
||||
return;
|
||||
}
|
||||
auto row = std::make_unique<PeerListRow>(chat);
|
||||
|
||||
@@ -222,7 +222,8 @@ MTPChatAdminRights EditAdminBox::defaultRights() const {
|
||||
| Flag::f_post_messages
|
||||
| Flag::f_edit_messages
|
||||
| Flag::f_delete_messages
|
||||
| Flag::f_invite_users);
|
||||
| Flag::f_invite_users
|
||||
| Flag::f_manage_call);
|
||||
return MTP_chatAdminRights(MTP_flags(flags));
|
||||
}
|
||||
|
||||
@@ -611,11 +612,11 @@ void EditRestrictedBox::prepare() {
|
||||
const auto defaultRestrictions = chat
|
||||
? chat->defaultRestrictions()
|
||||
: channel->defaultRestrictions();
|
||||
const auto prepareRights = _oldRights.c_chatBannedRights().vflags().v
|
||||
const auto prepareRights = Data::ChatBannedRightsFlags(_oldRights)
|
||||
? _oldRights
|
||||
: defaultRights();
|
||||
const auto prepareFlags = FixDependentRestrictions(
|
||||
prepareRights.c_chatBannedRights().vflags().v
|
||||
Data::ChatBannedRightsFlags(prepareRights)
|
||||
| defaultRestrictions
|
||||
| ((channel && channel->isPublic())
|
||||
? (Flag::f_change_info | Flag::f_pin_messages)
|
||||
@@ -646,7 +647,7 @@ void EditRestrictedBox::prepare() {
|
||||
disabledMessages);
|
||||
addControl(std::move(checkboxes), QMargins());
|
||||
|
||||
_until = prepareRights.c_chatBannedRights().vuntil_date().v;
|
||||
_until = Data::ChatBannedRightsUntilDate(prepareRights);
|
||||
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
|
||||
addControl(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -766,7 +767,7 @@ void EditRestrictedBox::createUntilVariants() {
|
||||
}
|
||||
};
|
||||
auto addCurrentVariant = [&](TimeId from, TimeId to) {
|
||||
auto oldUntil = _oldRights.c_chatBannedRights().vuntil_date().v;
|
||||
auto oldUntil = Data::ChatBannedRightsUntilDate(_oldRights);
|
||||
if (oldUntil < _until) {
|
||||
addCustomVariant(oldUntil, from, to);
|
||||
}
|
||||
|
||||
@@ -146,18 +146,18 @@ void SaveChannelAdmin(
|
||||
|
||||
void SaveChannelRestriction(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights,
|
||||
Fn<void()> onDone,
|
||||
Fn<void()> onFail) {
|
||||
channel->session().api().request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
participant->input,
|
||||
newRights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
channel->applyEditBanned(user, oldRights, newRights);
|
||||
channel->applyEditBanned(participant, oldRights, newRights);
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
@@ -243,7 +243,7 @@ Fn<void(
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
Fn<void(const MTPChatBannedRights &newRights)> onDone,
|
||||
Fn<void()> onFail) {
|
||||
return [=](
|
||||
@@ -253,19 +253,21 @@ Fn<void(
|
||||
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
||||
SaveChannelRestriction(
|
||||
channel,
|
||||
user,
|
||||
participant,
|
||||
oldRights,
|
||||
newRights,
|
||||
done,
|
||||
onFail);
|
||||
};
|
||||
if (const auto chat = peer->asChatNotMigrated()) {
|
||||
const auto flags = newRights.match([](
|
||||
const MTPDchatBannedRights &data) {
|
||||
return data.vflags().v;
|
||||
});
|
||||
if (flags & MTPDchatBannedRights::Flag::f_view_messages) {
|
||||
SaveChatParticipantKick(chat, user, done, onFail);
|
||||
const auto flags = Data::ChatBannedRightsFlags(newRights);
|
||||
if (participant->isUser()
|
||||
&& (flags & MTPDchatBannedRights::Flag::f_view_messages)) {
|
||||
SaveChatParticipantKick(
|
||||
chat,
|
||||
participant->asUser(),
|
||||
done,
|
||||
onFail);
|
||||
} else if (!flags) {
|
||||
done();
|
||||
} else {
|
||||
@@ -313,9 +315,9 @@ ParticipantsAdditionalData::ParticipantsAdditionalData(
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::infoLoaded(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
|| (_infoNotLoaded.find(user) == end(_infoNotLoaded));
|
||||
|| (_infoNotLoaded.find(participant) == end(_infoNotLoaded));
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canEditAdmin(
|
||||
@@ -342,24 +344,27 @@ bool ParticipantsAdditionalData::canAddOrEditAdmin(
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canAddOrEditAdmin.");
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canRestrictUser(
|
||||
not_null<UserData*> user) const {
|
||||
if (!canEditAdmin(user) || user->isSelf()) {
|
||||
bool ParticipantsAdditionalData::canRestrictParticipant(
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (user && (!canEditAdmin(user) || user->isSelf())) {
|
||||
return false;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->canBanMembers();
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
return channel->canBanMembers();
|
||||
}
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canRestrictParticipant.");
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canRemoveUser(
|
||||
not_null<UserData*> user) const {
|
||||
if (canRestrictUser(user)) {
|
||||
bool ParticipantsAdditionalData::canRemoveParticipant(
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (canRestrictParticipant(participant)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return !user->isSelf()
|
||||
return user
|
||||
&& !user->isSelf()
|
||||
&& chat->invitedByMe.contains(user)
|
||||
&& (chat->amCreator() || !_admins.contains(user));
|
||||
}
|
||||
@@ -388,12 +393,12 @@ QString ParticipantsAdditionalData::adminRank(
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::restrictedRights(
|
||||
not_null<UserData*> user) const
|
||||
not_null<PeerData*> participant) const
|
||||
-> std::optional<MTPChatBannedRights> {
|
||||
if (_peer->isChat()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = _restrictedRights.find(user);
|
||||
const auto i = _restrictedRights.find(participant);
|
||||
return (i != end(_restrictedRights))
|
||||
? std::make_optional(i->second)
|
||||
: std::nullopt;
|
||||
@@ -404,16 +409,18 @@ bool ParticipantsAdditionalData::isCreator(not_null<UserData*> user) const {
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::isExternal(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
? !_members.contains(user)
|
||||
: _external.find(user) != end(_external);
|
||||
? (participant->isUser()
|
||||
&& !_members.contains(participant->asUser()))
|
||||
: _external.find(participant) != end(_external);
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::isKicked(not_null<UserData*> user) const {
|
||||
bool ParticipantsAdditionalData::isKicked(
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
? false
|
||||
: _kicked.find(user) != end(_kicked);
|
||||
: _kicked.find(participant) != end(_kicked);
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::adminPromotedBy(
|
||||
@@ -426,29 +433,41 @@ UserData *ParticipantsAdditionalData::adminPromotedBy(
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::restrictedBy(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
if (_peer->isChat()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto i = _restrictedBy.find(user);
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
return (i != end(_restrictedBy)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
void ParticipantsAdditionalData::setExternal(not_null<UserData*> user) {
|
||||
_infoNotLoaded.erase(user);
|
||||
_external.emplace(user);
|
||||
void ParticipantsAdditionalData::setExternal(
|
||||
not_null<PeerData*> participant) {
|
||||
if (const auto user = participant->asUser()) {
|
||||
_adminRights.erase(user);
|
||||
_adminCanEdit.erase(user);
|
||||
_adminPromotedBy.erase(user);
|
||||
_admins.erase(user);
|
||||
}
|
||||
_restrictedRights.erase(participant);
|
||||
_kicked.erase(participant);
|
||||
_restrictedBy.erase(participant);
|
||||
_infoNotLoaded.erase(participant);
|
||||
_external.emplace(participant);
|
||||
}
|
||||
|
||||
void ParticipantsAdditionalData::checkForLoaded(not_null<UserData*> user) {
|
||||
void ParticipantsAdditionalData::checkForLoaded(
|
||||
not_null<PeerData*> participant) {
|
||||
const auto contains = [](const auto &map, const auto &value) {
|
||||
return map.find(value) != map.end();
|
||||
};
|
||||
if (_creator != user
|
||||
&& !contains(_adminRights, user)
|
||||
&& !contains(_restrictedRights, user)
|
||||
&& !contains(_external, user)
|
||||
&& !contains(_kicked, user)) {
|
||||
_infoNotLoaded.emplace(user);
|
||||
const auto user = participant->asUser();
|
||||
if (!(user && _creator == user)
|
||||
&& !(user && contains(_adminRights, user))
|
||||
&& !contains(_restrictedRights, participant)
|
||||
&& !contains(_external, participant)
|
||||
&& !contains(_kicked, participant)) {
|
||||
_infoNotLoaded.emplace(participant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,15 +529,15 @@ void ParticipantsAdditionalData::fillFromChannel(
|
||||
}
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
PeerData *ParticipantsAdditionalData::applyParticipant(
|
||||
const MTPChannelParticipant &data) {
|
||||
return applyParticipant(data, _role);
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
PeerData *ParticipantsAdditionalData::applyParticipant(
|
||||
const MTPChannelParticipant &data,
|
||||
Role overrideRole) {
|
||||
const auto logBad = [&]() -> UserData* {
|
||||
const auto logBad = [&]() -> PeerData* {
|
||||
LOG(("API Error: Bad participant type %1 got "
|
||||
"while requesting for participants, role: %2"
|
||||
).arg(data.type()
|
||||
@@ -526,27 +545,28 @@ UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
return data.match([&](const MTPDchannelParticipantCreator &data) {
|
||||
return data.match([&](
|
||||
const MTPDchannelParticipantCreator &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members
|
||||
&& overrideRole != Role::Admins) {
|
||||
return logBad();
|
||||
}
|
||||
return applyCreator(data);
|
||||
}, [&](const MTPDchannelParticipantAdmin &data) {
|
||||
}, [&](const MTPDchannelParticipantAdmin &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members
|
||||
&& overrideRole != Role::Admins) {
|
||||
return logBad();
|
||||
}
|
||||
return applyAdmin(data);
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
}, [&](const MTPDchannelParticipantSelf &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members) {
|
||||
return logBad();
|
||||
}
|
||||
return applyRegular(data.vuser_id());
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
}, [&](const MTPDchannelParticipant &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members) {
|
||||
return logBad();
|
||||
@@ -587,7 +607,7 @@ UserData *ParticipantsAdditionalData::applyCreator(
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
const MTPDchannelParticipantAdmin &data) {
|
||||
const auto user = _peer->owner().userLoaded(data.vuser_id().v);
|
||||
const auto user = _peer->owner().userLoaded(UserId(data.vuser_id().v));
|
||||
if (!user) {
|
||||
return nullptr;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
@@ -611,7 +631,7 @@ UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
} else {
|
||||
_adminRanks.remove(user);
|
||||
}
|
||||
if (const auto by = _peer->owner().userLoaded(data.vpromoted_by().v)) {
|
||||
if (const auto by = _peer->owner().userLoaded(data.vpromoted_by())) {
|
||||
const auto i = _adminPromotedBy.find(user);
|
||||
if (i == _adminPromotedBy.end()) {
|
||||
_adminPromotedBy.emplace(user, by);
|
||||
@@ -626,7 +646,7 @@ UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyRegular(MTPint userId) {
|
||||
const auto user = _peer->owner().userLoaded(userId.v);
|
||||
const auto user = _peer->owner().userLoaded(userId);
|
||||
if (!user) {
|
||||
return nullptr;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
@@ -645,32 +665,35 @@ UserData *ParticipantsAdditionalData::applyRegular(MTPint userId) {
|
||||
return user;
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyBanned(
|
||||
PeerData *ParticipantsAdditionalData::applyBanned(
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
const auto user = _peer->owner().userLoaded(data.vuser_id().v);
|
||||
if (!user) {
|
||||
const auto participant = _peer->owner().peerLoaded(
|
||||
peerFromMTP(data.vpeer()));
|
||||
if (!participant) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_infoNotLoaded.erase(user);
|
||||
_adminRights.erase(user);
|
||||
_adminCanEdit.erase(user);
|
||||
_adminPromotedBy.erase(user);
|
||||
if (data.is_left()) {
|
||||
_kicked.emplace(user);
|
||||
} else {
|
||||
_kicked.erase(user);
|
||||
_infoNotLoaded.erase(participant);
|
||||
if (const auto user = participant->asUser()) {
|
||||
_adminRights.erase(user);
|
||||
_adminCanEdit.erase(user);
|
||||
_adminPromotedBy.erase(user);
|
||||
}
|
||||
_restrictedRights[user] = data.vbanned_rights();
|
||||
if (const auto by = _peer->owner().userLoaded(data.vkicked_by().v)) {
|
||||
const auto i = _restrictedBy.find(user);
|
||||
if (data.is_left()) {
|
||||
_kicked.emplace(participant);
|
||||
} else {
|
||||
_kicked.erase(participant);
|
||||
}
|
||||
_restrictedRights[participant] = data.vbanned_rights();
|
||||
if (const auto by = _peer->owner().userLoaded(data.vkicked_by())) {
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
if (i == _restrictedBy.end()) {
|
||||
_restrictedBy.emplace(user, by);
|
||||
_restrictedBy.emplace(participant, by);
|
||||
} else {
|
||||
i->second = by;
|
||||
}
|
||||
}
|
||||
return user;
|
||||
return participant;
|
||||
}
|
||||
|
||||
void ParticipantsAdditionalData::migrate(
|
||||
@@ -702,7 +725,7 @@ ParticipantsOnlineSorter::ParticipantsOnlineSorter(
|
||||
Data::PeerUpdate::Flag::OnlineStatus
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
const auto peerId = update.peer->id;
|
||||
if (const auto row = _delegate->peerListFindRow(peerId)) {
|
||||
if (const auto row = _delegate->peerListFindRow(peerId.value)) {
|
||||
row->refreshStatus();
|
||||
sortDelayed();
|
||||
}
|
||||
@@ -804,7 +827,7 @@ void ParticipantsBoxController::setupListChangeViewers() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListPartitionRows([&](const PeerListRow &row) {
|
||||
return (row.peer() == user);
|
||||
});
|
||||
@@ -820,7 +843,7 @@ void ParticipantsBoxController::setupListChangeViewers() {
|
||||
channel->owner().megagroupParticipantRemoved(
|
||||
channel
|
||||
) | rpl::start_with_next([=](not_null<UserData*> user) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
@@ -922,9 +945,9 @@ void ParticipantsBoxController::addNewItem() {
|
||||
editAdminDone(user, rights, rank);
|
||||
});
|
||||
const auto restrictedDone = crl::guard(this, [=](
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights) {
|
||||
editRestrictedDone(user, rights);
|
||||
editRestrictedDone(participant, rights);
|
||||
});
|
||||
const auto initBox = [](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
@@ -955,8 +978,10 @@ void ParticipantsBoxController::addNewParticipants() {
|
||||
auto already = std::vector<not_null<UserData*>>();
|
||||
already.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
already.emplace_back(
|
||||
delegate()->peerListRowAt(i)->peer()->asUser());
|
||||
const auto participant = delegate()->peerListRowAt(i)->peer();
|
||||
if (const auto user = participant->asUser()) {
|
||||
already.emplace_back(user);
|
||||
}
|
||||
}
|
||||
AddParticipantsBoxController::Start(
|
||||
_navigation,
|
||||
@@ -1165,6 +1190,7 @@ void ParticipantsBoxController::rebuildChatParticipants(
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count;) {
|
||||
auto row = delegate()->peerListRowAt(i);
|
||||
Assert(row->peer()->isUser());
|
||||
auto user = row->peer()->asUser();
|
||||
if (participants.contains(user)) {
|
||||
++i;
|
||||
@@ -1316,8 +1342,9 @@ void ParticipantsBoxController::loadMoreRows() {
|
||||
int availableCount,
|
||||
const QVector<MTPChannelParticipant> &list) {
|
||||
for (const auto &data : list) {
|
||||
if (const auto user = _additional.applyParticipant(data)) {
|
||||
appendRow(user);
|
||||
if (const auto participant = _additional.applyParticipant(
|
||||
data)) {
|
||||
appendRow(participant);
|
||||
}
|
||||
}
|
||||
if (const auto size = list.size()) {
|
||||
@@ -1398,63 +1425,68 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
|
||||
const auto user = row->peer()->asUser();
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
if (_role == Role::Admins) {
|
||||
Assert(user != nullptr);
|
||||
showAdmin(user);
|
||||
} else if (_role == Role::Restricted
|
||||
&& (_peer->isChat() || _peer->isMegagroup())) {
|
||||
&& (_peer->isChat() || _peer->isMegagroup())
|
||||
&& user) {
|
||||
showRestricted(user);
|
||||
} else {
|
||||
Assert(_navigation != nullptr);
|
||||
_navigation->showPeerInfo(user);
|
||||
_navigation->showPeerInfo(participant);
|
||||
}
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::rowActionClicked(
|
||||
not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
|
||||
const auto user = row->peer()->asUser();
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
if (_role == Role::Members || _role == Role::Profile) {
|
||||
kickMember(user);
|
||||
kickParticipant(participant);
|
||||
} else if (_role == Role::Admins) {
|
||||
Assert(user != nullptr);
|
||||
removeAdmin(user);
|
||||
} else {
|
||||
removeKicked(row, user);
|
||||
removeKicked(row, participant);
|
||||
}
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
Expects(row->peer()->isUser());
|
||||
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
const auto user = row->peer()->asUser();
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
if (_navigation) {
|
||||
result->addAction(
|
||||
tr::lng_context_view_profile(tr::now),
|
||||
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
|
||||
(participant->isUser()
|
||||
? tr::lng_context_view_profile
|
||||
: participant->isBroadcast()
|
||||
? tr::lng_context_view_channel
|
||||
: tr::lng_context_view_group)(tr::now),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->showPeerInfo(participant); }));
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictUser(user)) {
|
||||
if (channel->canAddMembers()) {
|
||||
&& _additional.canRestrictParticipant(participant)) {
|
||||
if (user && channel->canAddMembers()) {
|
||||
result->addAction(
|
||||
tr::lng_context_add_to_group(tr::now),
|
||||
crl::guard(this, [=] { unkickMember(user); }));
|
||||
crl::guard(this, [=] { unkickParticipant(user); }));
|
||||
}
|
||||
result->addAction(
|
||||
tr::lng_profile_delete_removed(tr::now),
|
||||
crl::guard(this, [=] { removeKickedWithRow(user); }));
|
||||
crl::guard(this, [=] { removeKickedWithRow(participant); }));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
if (user && _additional.canAddOrEditAdmin(user)) {
|
||||
const auto isAdmin = _additional.isCreator(user)
|
||||
|| _additional.adminRights(user).has_value();
|
||||
result->addAction(
|
||||
@@ -1463,12 +1495,12 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
: tr::lng_context_promote_admin)(tr::now),
|
||||
crl::guard(this, [=] { showAdmin(user); }));
|
||||
}
|
||||
if (_additional.canRestrictUser(user)) {
|
||||
if (user && _additional.canRestrictParticipant(participant)) {
|
||||
const auto canRestrictWithoutKick = [&] {
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
return chat->amCreator();
|
||||
}
|
||||
return _peer->isMegagroup();
|
||||
return _peer->isMegagroup() && !_peer->isGigagroup();
|
||||
}();
|
||||
if (canRestrictWithoutKick) {
|
||||
result->addAction(
|
||||
@@ -1476,14 +1508,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
crl::guard(this, [=] { showRestricted(user); }));
|
||||
}
|
||||
}
|
||||
if (_additional.canRemoveUser(user)) {
|
||||
if (!_additional.isKicked(user)) {
|
||||
if (user && _additional.canRemoveParticipant(participant)) {
|
||||
if (!_additional.isKicked(participant)) {
|
||||
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
|
||||
result->addAction(
|
||||
(isGroup
|
||||
? tr::lng_context_remove_from_group
|
||||
: tr::lng_profile_kick)(tr::now),
|
||||
crl::guard(this, [=] { kickMember(user); }));
|
||||
crl::guard(this, [=] { kickParticipant(user); }));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1531,12 +1563,12 @@ void ParticipantsBoxController::editAdminDone(
|
||||
using Flag = MTPDchannelParticipantCreator::Flag;
|
||||
_additional.applyParticipant(MTP_channelParticipantCreator(
|
||||
MTP_flags(rank.isEmpty() ? Flag(0) : Flag::f_rank),
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
rights,
|
||||
MTP_string(rank)));
|
||||
} else if (rights.c_chatAdminRights().vflags().v == 0) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
MTP_int(date)));
|
||||
if (_role == Role::Admins) {
|
||||
removeRow(user);
|
||||
@@ -1547,11 +1579,11 @@ void ParticipantsBoxController::editAdminDone(
|
||||
_additional.applyParticipant(MTP_channelParticipantAdmin(
|
||||
MTP_flags(Flag::f_can_edit
|
||||
| (rank.isEmpty() ? Flag(0) : Flag::f_rank)),
|
||||
MTP_int(user->bareId()),
|
||||
peerToBareMTPInt(user->id),
|
||||
MTPint(), // inviter_id
|
||||
MTP_int(alreadyPromotedBy
|
||||
? alreadyPromotedBy->bareId()
|
||||
: user->session().userId()),
|
||||
peerToBareMTPInt(alreadyPromotedBy
|
||||
? alreadyPromotedBy->id
|
||||
: user->session().userPeerId()),
|
||||
MTP_int(date),
|
||||
rights,
|
||||
MTP_string(rank)));
|
||||
@@ -1569,9 +1601,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
const auto restrictedRights = _additional.restrictedRights(user);
|
||||
const auto currentRights = restrictedRights
|
||||
? *restrictedRights
|
||||
: MTPChatBannedRights(MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0)));
|
||||
: ChannelData::EmptyRestrictedRights(user);
|
||||
const auto hasAdminRights = _additional.adminRights(user).has_value();
|
||||
auto box = Box<EditRestrictedBox>(
|
||||
_peer,
|
||||
@@ -1580,7 +1610,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
currentRights);
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (_additional.canRestrictUser(user)) {
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
const MTPChatBannedRights &newRights) {
|
||||
editRestrictedDone(user, newRights);
|
||||
@@ -1597,99 +1627,106 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::editRestrictedDone(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights) {
|
||||
_addBox = nullptr;
|
||||
if (_editParticipantBox) {
|
||||
_editParticipantBox->closeBox();
|
||||
}
|
||||
|
||||
const auto user = participant->asUser();
|
||||
const auto date = base::unixtime::now(); // Incorrect, but ignored.
|
||||
if (rights.c_chatBannedRights().vflags().v == 0) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
MTP_int(user->bareId()),
|
||||
MTP_int(date)));
|
||||
if (Data::ChatBannedRightsFlags(rights) == 0) {
|
||||
if (user) {
|
||||
_additional.applyParticipant(MTP_channelParticipant(
|
||||
peerToBareMTPInt(user->id),
|
||||
MTP_int(date)));
|
||||
} else {
|
||||
_additional.setExternal(participant);
|
||||
}
|
||||
if (_role == Role::Kicked || _role == Role::Restricted) {
|
||||
removeRow(user);
|
||||
removeRow(participant);
|
||||
}
|
||||
} else {
|
||||
const auto kicked = rights.c_chatBannedRights().is_view_messages();
|
||||
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
|
||||
const auto kicked = Data::ChatBannedRightsFlags(rights)
|
||||
& ChatRestriction::f_view_messages;
|
||||
const auto alreadyRestrictedBy = _additional.restrictedBy(
|
||||
participant);
|
||||
_additional.applyParticipant(MTP_channelParticipantBanned(
|
||||
MTP_flags(kicked
|
||||
? MTPDchannelParticipantBanned::Flag::f_left
|
||||
: MTPDchannelParticipantBanned::Flag(0)),
|
||||
MTP_int(user->bareId()),
|
||||
MTP_int(alreadyRestrictedBy
|
||||
? alreadyRestrictedBy->bareId()
|
||||
: user->session().userId()),
|
||||
peerToMTP(participant->id),
|
||||
peerToBareMTPInt(alreadyRestrictedBy
|
||||
? alreadyRestrictedBy->id
|
||||
: participant->session().userPeerId()),
|
||||
MTP_int(date),
|
||||
rights));
|
||||
if (kicked) {
|
||||
if (_role == Role::Kicked) {
|
||||
prependRow(user);
|
||||
prependRow(participant);
|
||||
} else if (_role == Role::Admins
|
||||
|| _role == Role::Restricted
|
||||
|| _role == Role::Members) {
|
||||
removeRow(user);
|
||||
removeRow(participant);
|
||||
}
|
||||
} else {
|
||||
if (_role == Role::Restricted) {
|
||||
prependRow(user);
|
||||
prependRow(participant);
|
||||
} else if (_role == Role::Kicked
|
||||
|| _role == Role::Admins
|
||||
|| _role == Role::Members) {
|
||||
removeRow(user);
|
||||
removeRow(participant);
|
||||
}
|
||||
}
|
||||
}
|
||||
recomputeTypeFor(user);
|
||||
recomputeTypeFor(participant);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::kickMember(not_null<UserData*> user) {
|
||||
void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant) {
|
||||
const auto user = participant->asUser();
|
||||
const auto text = ((_peer->isChat() || _peer->isMegagroup())
|
||||
? tr::lng_profile_sure_kick
|
||||
: tr::lng_profile_sure_kick_channel)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
user->firstName);
|
||||
user ? user->firstName : participant->name);
|
||||
_editBox = Ui::show(
|
||||
Box<ConfirmBox>(
|
||||
text,
|
||||
tr::lng_box_remove(tr::now),
|
||||
crl::guard(this, [=] { kickMemberSure(user); })),
|
||||
crl::guard(this, [=] { kickParticipantSure(participant); })),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
|
||||
void ParticipantsBoxController::unkickParticipant(not_null<UserData*> user) {
|
||||
_editBox = nullptr;
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
_peer->session().api().addChatParticipants(_peer, { 1, user });
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) {
|
||||
void ParticipantsBoxController::kickParticipantSure(
|
||||
not_null<PeerData*> participant) {
|
||||
_editBox = nullptr;
|
||||
|
||||
const auto restrictedRights = _additional.restrictedRights(user);
|
||||
const auto restrictedRights = _additional.restrictedRights(participant);
|
||||
const auto currentRights = restrictedRights
|
||||
? *restrictedRights
|
||||
: MTPChatBannedRights(MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0)));
|
||||
: ChannelData::EmptyRestrictedRights(participant);
|
||||
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
auto &session = _peer->session();
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
session.api().kickParticipant(chat, user);
|
||||
session.api().kickParticipant(chat, participant);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
session.api().kickParticipant(channel, user, currentRights);
|
||||
session.api().kickParticipant(channel, participant, currentRights);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1730,36 +1767,37 @@ void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::removeKickedWithRow(
|
||||
not_null<UserData*> user) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
removeKicked(row, user);
|
||||
not_null<PeerData*> participant) {
|
||||
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
removeKicked(row, participant);
|
||||
} else {
|
||||
removeKicked(user);
|
||||
removeKicked(participant);
|
||||
}
|
||||
}
|
||||
void ParticipantsBoxController::removeKicked(not_null<UserData*> user) {
|
||||
void ParticipantsBoxController::removeKicked(
|
||||
not_null<PeerData*> participant) {
|
||||
if (const auto channel = _peer->asChannel()) {
|
||||
channel->session().api().unblockParticipant(channel, user);
|
||||
channel->session().api().unblockParticipant(channel, participant);
|
||||
}
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::removeKicked(
|
||||
not_null<PeerListRow*> row,
|
||||
not_null<UserData*> user) {
|
||||
not_null<PeerData*> participant) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
if (_role != Role::Kicked
|
||||
&& !delegate()->peerListFullRowsCount()) {
|
||||
setDescriptionText(tr::lng_blocked_list_not_found(tr::now));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
removeKicked(user);
|
||||
removeKicked(participant);
|
||||
}
|
||||
|
||||
bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
|
||||
if (delegate()->peerListFindRow(user->id)) {
|
||||
recomputeTypeFor(user);
|
||||
bool ParticipantsBoxController::appendRow(not_null<PeerData*> participant) {
|
||||
if (delegate()->peerListFindRow(participant->id.value)) {
|
||||
recomputeTypeFor(participant);
|
||||
return false;
|
||||
} else if (auto row = createRow(user)) {
|
||||
} else if (auto row = createRow(participant)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
@@ -1769,16 +1807,16 @@ bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
recomputeTypeFor(user);
|
||||
bool ParticipantsBoxController::prependRow(not_null<PeerData*> participant) {
|
||||
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
recomputeTypeFor(participant);
|
||||
refreshCustomStatus(row);
|
||||
if (_role == Role::Admins) {
|
||||
// Perhaps we've added a new admin from search.
|
||||
delegate()->peerListPrependRowFromSearchResult(row);
|
||||
}
|
||||
return false;
|
||||
} else if (auto row = createRow(user)) {
|
||||
} else if (auto row = createRow(participant)) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
@@ -1788,8 +1826,8 @@ bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
|
||||
if (auto row = delegate()->peerListFindRow(user->id)) {
|
||||
bool ParticipantsBoxController::removeRow(not_null<PeerData*> participant) {
|
||||
if (auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
if (_role == Role::Admins) {
|
||||
// Perhaps we are removing an admin from search results.
|
||||
row->setCustomStatus(tr::lng_channel_admin_status_not_admin(tr::now));
|
||||
@@ -1807,24 +1845,28 @@ bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (_role == Role::Profile) {
|
||||
Assert(user != nullptr);
|
||||
return std::make_unique<Row>(user, computeType(user));
|
||||
}
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
auto row = std::make_unique<PeerListRowWithLink>(user);
|
||||
auto row = std::make_unique<PeerListRowWithLink>(participant);
|
||||
refreshCustomStatus(row.get());
|
||||
if (_role == Role::Admins
|
||||
&& user
|
||||
&& !_additional.isCreator(user)
|
||||
&& _additional.adminRights(user).has_value()
|
||||
&& _additional.canEditAdmin(user)) {
|
||||
row->setActionLink(tr::lng_profile_kick(tr::now));
|
||||
} else if (_role == Role::Kicked || _role == Role::Restricted) {
|
||||
if (_additional.canRestrictUser(user)) {
|
||||
if (_additional.canRestrictParticipant(participant)) {
|
||||
row->setActionLink(tr::lng_profile_delete_removed(tr::now));
|
||||
}
|
||||
} else if (_role == Role::Members) {
|
||||
Assert(user != nullptr);
|
||||
if ((chat ? chat->canBanMembers() : channel->canBanMembers())
|
||||
&& !_additional.isCreator(user)
|
||||
&& (!_additional.adminRights(user)
|
||||
@@ -1842,31 +1884,34 @@ std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
|
||||
}
|
||||
|
||||
auto ParticipantsBoxController::computeType(
|
||||
not_null<UserData*> user) const -> Type {
|
||||
not_null<PeerData*> participant) const -> Type {
|
||||
const auto user = participant->asUser();
|
||||
auto result = Type();
|
||||
result.rights = _additional.isCreator(user)
|
||||
result.rights = (user && _additional.isCreator(user))
|
||||
? Rights::Creator
|
||||
: _additional.adminRights(user).has_value()
|
||||
: (user && _additional.adminRights(user).has_value())
|
||||
? Rights::Admin
|
||||
: Rights::Normal;
|
||||
result.canRemove = _additional.canRemoveUser(user);
|
||||
result.canRemove = _additional.canRemoveParticipant(participant);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::recomputeTypeFor(
|
||||
not_null<UserData*> user) {
|
||||
not_null<PeerData*> participant) {
|
||||
if (_role != Role::Profile) {
|
||||
return;
|
||||
}
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
static_cast<Row*>(row)->setType(computeType(user));
|
||||
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
static_cast<Row*>(row)->setType(computeType(participant));
|
||||
}
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::refreshCustomStatus(
|
||||
not_null<PeerListRow*> row) const {
|
||||
const auto user = row->peer()->asUser();
|
||||
const auto participant = row->peer();
|
||||
const auto user = participant->asUser();
|
||||
if (_role == Role::Admins) {
|
||||
Assert(user != nullptr);
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
row->setCustomStatus(tr::lng_channel_admin_status_promoted_by(
|
||||
tr::now,
|
||||
@@ -1882,7 +1927,7 @@ void ParticipantsBoxController::refreshCustomStatus(
|
||||
}
|
||||
}
|
||||
} else if (_role == Role::Kicked || _role == Role::Restricted) {
|
||||
const auto by = _additional.restrictedBy(user);
|
||||
const auto by = _additional.restrictedBy(participant);
|
||||
row->setCustomStatus((_role == Role::Kicked
|
||||
? tr::lng_channel_banned_status_removed_by
|
||||
: tr::lng_channel_banned_status_restricted_by)(
|
||||
|
||||
@@ -33,7 +33,7 @@ Fn<void(
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
Fn<void(const MTPChatBannedRights &newRights)> onDone,
|
||||
Fn<void()> onFail);
|
||||
|
||||
@@ -77,29 +77,31 @@ public:
|
||||
|
||||
ParticipantsAdditionalData(not_null<PeerData*> peer, Role role);
|
||||
|
||||
UserData *applyParticipant(const MTPChannelParticipant &data);
|
||||
UserData *applyParticipant(
|
||||
PeerData *applyParticipant(const MTPChannelParticipant &data);
|
||||
PeerData *applyParticipant(
|
||||
const MTPChannelParticipant &data,
|
||||
Role overrideRole);
|
||||
void setExternal(not_null<UserData*> user);
|
||||
void checkForLoaded(not_null<UserData*> user);
|
||||
void setExternal(not_null<PeerData*> participant);
|
||||
void checkForLoaded(not_null<PeerData*> participant);
|
||||
void fillFromPeer();
|
||||
|
||||
[[nodiscard]] bool infoLoaded(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool infoLoaded(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictParticipant(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool canRemoveParticipant(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<MTPChatBannedRights> restrictedRights(
|
||||
not_null<UserData*> user) const;
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isKicked(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
|
||||
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -107,7 +109,7 @@ private:
|
||||
UserData *applyCreator(const MTPDchannelParticipantCreator &data);
|
||||
UserData *applyAdmin(const MTPDchannelParticipantAdmin &data);
|
||||
UserData *applyRegular(MTPint userId);
|
||||
UserData *applyBanned(const MTPDchannelParticipantBanned &data);
|
||||
PeerData *applyBanned(const MTPDchannelParticipantBanned &data);
|
||||
void fillFromChat(not_null<ChatData*> chat);
|
||||
void fillFromChannel(not_null<ChannelData*> channel);
|
||||
|
||||
@@ -124,11 +126,11 @@ private:
|
||||
base::flat_map<not_null<UserData*>, QString> _adminRanks;
|
||||
base::flat_set<not_null<UserData*>> _adminCanEdit;
|
||||
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
|
||||
std::map<not_null<UserData*>, MTPChatBannedRights> _restrictedRights;
|
||||
std::set<not_null<UserData*>> _kicked;
|
||||
std::map<not_null<UserData*>, not_null<UserData*>> _restrictedBy;
|
||||
std::set<not_null<UserData*>> _external;
|
||||
std::set<not_null<UserData*>> _infoNotLoaded;
|
||||
std::map<not_null<PeerData*>, MTPChatBannedRights> _restrictedRights;
|
||||
std::set<not_null<PeerData*>> _kicked;
|
||||
std::map<not_null<PeerData*>, not_null<UserData*>> _restrictedBy;
|
||||
std::set<not_null<PeerData*>> _external;
|
||||
std::set<not_null<PeerData*>> _infoNotLoaded;
|
||||
|
||||
};
|
||||
|
||||
@@ -181,7 +183,7 @@ protected:
|
||||
Role role);
|
||||
|
||||
virtual std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
private:
|
||||
using Row = Info::Profile::MemberListRow;
|
||||
@@ -223,23 +225,25 @@ private:
|
||||
const QString &rank);
|
||||
void showRestricted(not_null<UserData*> user);
|
||||
void editRestrictedDone(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights);
|
||||
void removeKicked(not_null<PeerListRow*> row, not_null<UserData*> user);
|
||||
void removeKickedWithRow(not_null<UserData*> user);
|
||||
void removeKicked(not_null<UserData*> user);
|
||||
void kickMember(not_null<UserData*> user);
|
||||
void kickMemberSure(not_null<UserData*> user);
|
||||
void unkickMember(not_null<UserData*> user);
|
||||
void removeKicked(
|
||||
not_null<PeerListRow*> row,
|
||||
not_null<PeerData*> participant);
|
||||
void removeKickedWithRow(not_null<PeerData*> participant);
|
||||
void removeKicked(not_null<PeerData*> participant);
|
||||
void kickParticipant(not_null<PeerData*> participant);
|
||||
void kickParticipantSure(not_null<PeerData*> participant);
|
||||
void unkickParticipant(not_null<UserData*> user);
|
||||
void removeAdmin(not_null<UserData*> user);
|
||||
void removeAdminSure(not_null<UserData*> user);
|
||||
bool appendRow(not_null<UserData*> user);
|
||||
bool prependRow(not_null<UserData*> user);
|
||||
bool removeRow(not_null<UserData*> user);
|
||||
bool appendRow(not_null<PeerData*> participant);
|
||||
bool prependRow(not_null<PeerData*> participant);
|
||||
bool removeRow(not_null<PeerData*> participant);
|
||||
void refreshCustomStatus(not_null<PeerListRow*> row) const;
|
||||
bool feedMegagroupLastParticipants();
|
||||
Type computeType(not_null<UserData*> user) const;
|
||||
void recomputeTypeFor(not_null<UserData*> user);
|
||||
Type computeType(not_null<PeerData*> participant) const;
|
||||
void recomputeTypeFor(not_null<PeerData*> participant);
|
||||
|
||||
void subscribeToMigration();
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/single_choice_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
@@ -20,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
|
||||
@@ -766,7 +766,7 @@ void AdminsController::prepare() {
|
||||
owner.processUsers(data.vusers());
|
||||
for (const auto &admin : data.vadmins().v) {
|
||||
admin.match([&](const MTPDchatAdminWithInvites &data) {
|
||||
const auto adminId = data.vadmin_id().v;
|
||||
const auto adminId = data.vadmin_id();
|
||||
if (const auto user = owner.userLoaded(adminId)) {
|
||||
if (!user->isSelf()) {
|
||||
appendRow(user, data.vinvites_count().v);
|
||||
|
||||
@@ -151,6 +151,7 @@ std::vector<std::pair<ChatAdminRights, QString>> AdminRightLabels(
|
||||
{ Flag::f_edit_messages, tr::lng_rights_channel_edit(tr::now) },
|
||||
{ Flag::f_delete_messages, tr::lng_rights_channel_delete(tr::now) },
|
||||
{ Flag::f_invite_users, tr::lng_rights_group_invite(tr::now) },
|
||||
{ Flag::f_manage_call, tr::lng_rights_group_manage_calls(tr::now) },
|
||||
{ Flag::f_add_admins, tr::lng_rights_add_admins(tr::now) }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ void ShareBox::prepare() {
|
||||
applyFilterUpdate(query);
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(itemId)) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
_inner->peerUnselected(peer);
|
||||
selectedChanged();
|
||||
update();
|
||||
@@ -469,7 +469,7 @@ void ShareBox::addPeerToMultiSelect(PeerData *peer, bool skipAnimation) {
|
||||
using AddItemWay = Ui::MultiSelect::AddItemWay;
|
||||
auto addItemWay = skipAnimation ? AddItemWay::SkipAnimation : AddItemWay::Default;
|
||||
_select->addItem(
|
||||
peer->id,
|
||||
peer->id.value,
|
||||
peer->isSelf() ? tr::lng_saved_short(tr::now) : peer->shortName(),
|
||||
st::activeButtonBg,
|
||||
PaintUserpicCallback(peer, true),
|
||||
@@ -481,7 +481,7 @@ void ShareBox::innerSelectedChanged(PeerData *peer, bool checked) {
|
||||
addPeerToMultiSelect(peer);
|
||||
_select->clearQuery();
|
||||
} else {
|
||||
_select->removeItem(peer->id);
|
||||
_select->removeItem(peer->id.value);
|
||||
}
|
||||
selectedChanged();
|
||||
update();
|
||||
@@ -1107,25 +1107,25 @@ QString AppendShareGameScoreUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const FullMsgId &fullId) {
|
||||
auto shareHashData = QByteArray(0x10, Qt::Uninitialized);
|
||||
auto shareHashDataInts = reinterpret_cast<int32*>(shareHashData.data());
|
||||
auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
|
||||
auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data());
|
||||
auto channel = fullId.channel
|
||||
? session->data().channelLoaded(fullId.channel)
|
||||
: static_cast<ChannelData*>(nullptr);
|
||||
auto channelAccessHash = channel ? channel->access : 0ULL;
|
||||
auto channelAccessHash = uint64(channel ? channel->access : 0);
|
||||
auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash);
|
||||
shareHashDataInts[0] = session->userId();
|
||||
shareHashDataInts[1] = fullId.channel;
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.channel.bare;
|
||||
shareHashDataInts[2] = fullId.msg;
|
||||
shareHashDataInts[3] = channelAccessHashInts[0];
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
auto key128Size = 0x10;
|
||||
auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
|
||||
hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
|
||||
|
||||
// Mix in channel access hash to the first 64 bits of SHA1 of data.
|
||||
*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= *reinterpret_cast<uint64*>(channelAccessHashInts);
|
||||
//// Mix in channel access hash to the first 64 bits of SHA1 of data.
|
||||
//*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash;
|
||||
|
||||
// Encrypt data.
|
||||
if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
|
||||
@@ -1157,7 +1157,7 @@ void ShareGameScoreByHash(
|
||||
auto key128Size = 0x10;
|
||||
|
||||
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() % 0x10) != 0) {
|
||||
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
|
||||
Ui::show(Box<InformBox>(tr::lng_confirm_phone_link_invalid(tr::now)));
|
||||
return;
|
||||
}
|
||||
@@ -1172,37 +1172,46 @@ void ShareGameScoreByHash(
|
||||
char dataSha1[20] = { 0 };
|
||||
hashSha1(hashData.constData(), hashData.size(), dataSha1);
|
||||
|
||||
// Mix out channel access hash from the first 64 bits of SHA1 of data.
|
||||
auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
|
||||
//// Mix out channel access hash from the first 64 bits of SHA1 of data.
|
||||
//auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
|
||||
|
||||
// Check next 64 bits of SHA1() of data.
|
||||
auto skipSha1Part = sizeof(channelAccessHash);
|
||||
if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
|
||||
//// 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)));
|
||||
// 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)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto hashDataInts = reinterpret_cast<int32*>(hashData.data());
|
||||
if (hashDataInts[0] != session->userId()) {
|
||||
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
|
||||
if (hashDataInts[0] != session->userId().bare) {
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check first 32 bits of channel access hash.
|
||||
auto channelAccessHashInts = reinterpret_cast<int32*>(&channelAccessHash);
|
||||
if (channelAccessHashInts[0] != hashDataInts[3]) {
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
return;
|
||||
}
|
||||
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)));
|
||||
// return;
|
||||
//}
|
||||
|
||||
auto channelId = hashDataInts[1];
|
||||
auto msgId = hashDataInts[2];
|
||||
if (!channelId && channelAccessHash) {
|
||||
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)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto channelId = ChannelId(hashDataInts[1]);
|
||||
auto msgId = MsgId(hashDataInts[2]);
|
||||
if (const auto item = session->data().message(channelId, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
@@ -1228,7 +1237,7 @@ void ShareGameScoreByHash(
|
||||
MTP_vector<MTPInputChannel>(
|
||||
1,
|
||||
MTP_inputChannel(
|
||||
MTP_int(channelId),
|
||||
MTP_int(channelId.bare), // #TODO ids
|
||||
MTP_long(channelAccessHash)))
|
||||
)).done([=](const MTPmessages_Chats &result) {
|
||||
result.match([&](const auto &data) {
|
||||
|
||||
@@ -208,6 +208,55 @@ callRemoteAudioMute: FlatLabel(callStatus) {
|
||||
}
|
||||
callRemoteAudioMuteSkip: 12px;
|
||||
|
||||
callMuteMainBlobMinRadius: 57px;
|
||||
callMuteMainBlobMaxRadius: 63px;
|
||||
callMuteMinorBlobMinRadius: 64px;
|
||||
callMuteMinorBlobMaxRadius: 74px;
|
||||
callMuteMajorBlobMinRadius: 67px;
|
||||
callMuteMajorBlobMaxRadius: 77px;
|
||||
|
||||
callMuteButtonActiveInner: IconButton {
|
||||
width: 136px;
|
||||
height: 165px;
|
||||
}
|
||||
callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px underline);
|
||||
}
|
||||
}
|
||||
callMuteButtonSublabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
callMuteButtonLabelsSkip: 5px;
|
||||
callMuteButtonSublabelSkip: 19px;
|
||||
callMuteButtonActive: CallButton {
|
||||
button: callMuteButtonActiveInner;
|
||||
bg: groupCallLive1;
|
||||
bgSize: 100px;
|
||||
bgPosition: point(18px, 18px);
|
||||
outerRadius: 18px;
|
||||
outerBg: callAnswerBgOuter;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonMuted: CallButton(callMuteButtonActive) {
|
||||
bg: groupCallMuted1;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonConnecting: CallButton(callMuteButtonMuted) {
|
||||
bg: callIconBg;
|
||||
label: callMuteButtonLabel;
|
||||
}
|
||||
callMuteButtonLabelAdditional: 5px;
|
||||
|
||||
callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: lightButtonFg;
|
||||
thickness: 4px;
|
||||
size: size(100px, 100px);
|
||||
}
|
||||
|
||||
callBarHeight: 38px;
|
||||
callBarMuteToggle: IconButton {
|
||||
width: 41px;
|
||||
@@ -393,8 +442,8 @@ callErrorToast: Toast(defaultToast) {
|
||||
groupCallWidth: 380px;
|
||||
groupCallHeight: 580px;
|
||||
|
||||
groupCallMuteButtonIconSize: size(55px, 55px);
|
||||
groupCallMuteButtonIconTop: 42px;
|
||||
groupCallMuteButtonIconSize: size(67px, 67px);
|
||||
groupCallMuteButtonIconTop: 35px;
|
||||
groupCallRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallMembersBgRipple;
|
||||
}
|
||||
@@ -685,13 +734,19 @@ groupCallMemberInvited: icon {{ "calls/group_calls_invited", groupCallMemberInac
|
||||
groupCallMemberInvitedPosition: point(2px, 12px);
|
||||
groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMemberInactiveStatus }};
|
||||
|
||||
groupCallSettingsInner: IconButton(callButton) {
|
||||
iconPosition: point(-1px, 22px);
|
||||
icon: icon {{ "calls/call_settings", groupCallIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callMuteRipple;
|
||||
}
|
||||
}
|
||||
groupCallSettings: CallButton(callMicrophoneMute) {
|
||||
button: IconButton(callButton) {
|
||||
iconPosition: point(-1px, 22px);
|
||||
icon: icon {{ "calls/call_settings", groupCallIconFg }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: callMuteRipple;
|
||||
}
|
||||
button: groupCallSettingsInner;
|
||||
}
|
||||
groupCallShare: CallButton(groupCallSettings) {
|
||||
button: IconButton(groupCallSettingsInner) {
|
||||
icon: icon {{ "calls/group_calls_share", groupCallIconFg }};
|
||||
}
|
||||
}
|
||||
groupCallHangup: CallButton(callHangup) {
|
||||
@@ -720,6 +775,11 @@ groupCallTopBarJoin: RoundButton(defaultActiveButton) {
|
||||
height: 26px;
|
||||
textTop: 4px;
|
||||
}
|
||||
groupCallTopBarOpen: RoundButton(groupCallTopBarJoin) {
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: shadowFg;
|
||||
}
|
||||
}
|
||||
groupCallBox: Box(defaultBox) {
|
||||
button: RoundButton(defaultBoxButton) {
|
||||
textFg: groupCallActiveFg;
|
||||
@@ -940,3 +1000,18 @@ callTopBarMuteCrossLine: CrossLineAnimation {
|
||||
endPosition: point(26px, 23px);
|
||||
stroke: 2px;
|
||||
}
|
||||
|
||||
groupCallStartsIn: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(20px semibold);
|
||||
linkFont: font(20px semibold);
|
||||
linkFontOver: font(20px semibold underline);
|
||||
}
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
groupCallScheduledBodyHeight: 200px;
|
||||
groupCallStartsWhen: groupCallStartsIn;
|
||||
groupCallStartsInTop: 10px;
|
||||
groupCallStartsWhenTop: 160px;
|
||||
groupCallCountdownFont: font(64px semibold);
|
||||
groupCallCountdownTop: 52px;
|
||||
|
||||
@@ -402,7 +402,8 @@ void BoxController::receivedCalls(const QVector<MTPMessage> &result) {
|
||||
NewMessageType::Existing);
|
||||
insertRow(item, InsertWay::Append);
|
||||
} else {
|
||||
LOG(("API Error: a search results with not loaded peer %1").arg(peerId));
|
||||
LOG(("API Error: a search results with not loaded peer %1"
|
||||
).arg(peerId.value));
|
||||
}
|
||||
_offsetId = msgId;
|
||||
}
|
||||
|
||||
@@ -490,13 +490,13 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
auto &data = call.c_phoneCallRequested();
|
||||
if (_type != Type::Incoming
|
||||
|| _id != 0
|
||||
|| peerToUser(_user->id) != data.vadmin_id().v) {
|
||||
|| peerToUser(_user->id) != UserId(data.vadmin_id())) {
|
||||
Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
|
||||
}
|
||||
if (_user->session().userId() != data.vparticipant_id().v) {
|
||||
if (_user->session().userId() != UserId(data.vparticipant_id())) {
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2."
|
||||
).arg(data.vparticipant_id().v
|
||||
).arg(_user->session().userId()));
|
||||
).arg(_user->session().userId().bare));
|
||||
finish(FinishType::Failed);
|
||||
return true;
|
||||
}
|
||||
@@ -891,12 +891,12 @@ bool Call::checkCallCommonFields(const T &call) {
|
||||
}
|
||||
auto adminId = (_type == Type::Outgoing) ? _user->session().userId() : peerToUser(_user->id);
|
||||
auto participantId = (_type == Type::Outgoing) ? peerToUser(_user->id) : _user->session().userId();
|
||||
if (call.vadmin_id().v != adminId) {
|
||||
LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id().v).arg(adminId));
|
||||
if (UserId(call.vadmin_id()) != adminId) {
|
||||
LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id().v).arg(adminId.bare));
|
||||
return checkFailed();
|
||||
}
|
||||
if (call.vparticipant_id().v != participantId) {
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id().v).arg(participantId));
|
||||
if (UserId(call.vparticipant_id()) != participantId) {
|
||||
LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id().v).arg(participantId.bare));
|
||||
return checkFailed();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/calls_choose_join_as.h"
|
||||
|
||||
#include "calls/calls_group_common.h"
|
||||
#include "calls/calls_group_menu.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -18,15 +19,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/boxes/choose_date_time.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
namespace Calls::Group {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDefaultScheduleDuration = 60 * TimeId(60);
|
||||
constexpr auto kLabelRefreshInterval = 10 * crl::time(1000);
|
||||
|
||||
using Context = ChooseJoinAsProcess::Context;
|
||||
|
||||
class ListController : public PeerListController {
|
||||
@@ -98,7 +105,7 @@ void ListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
if (peer == _selected) {
|
||||
return;
|
||||
}
|
||||
const auto previous = delegate()->peerListFindRow(_selected->id);
|
||||
const auto previous = delegate()->peerListFindRow(_selected->id.value);
|
||||
Assert(previous != nullptr);
|
||||
delegate()->peerListSetRowChecked(previous, false);
|
||||
delegate()->peerListSetRowChecked(row, true);
|
||||
@@ -109,6 +116,79 @@ not_null<PeerData*> ListController::selected() const {
|
||||
return _selected;
|
||||
}
|
||||
|
||||
void ScheduleGroupCallBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const JoinInfo &info,
|
||||
Fn<void(JoinInfo)> done) {
|
||||
const auto send = [=](TimeId date) {
|
||||
box->closeBox();
|
||||
|
||||
auto copy = info;
|
||||
copy.scheduleDate = date;
|
||||
done(std::move(copy));
|
||||
};
|
||||
const auto duration = box->lifetime().make_state<
|
||||
rpl::variable<QString>>();
|
||||
auto description = (info.peer->isBroadcast()
|
||||
? tr::lng_group_call_schedule_notified_channel
|
||||
: tr::lng_group_call_schedule_notified_group)(
|
||||
lt_duration,
|
||||
duration->value());
|
||||
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
const auto min = [] {
|
||||
return base::unixtime::serialize(
|
||||
QDateTime::currentDateTime().addSecs(12));
|
||||
};
|
||||
const auto max = [] {
|
||||
return base::unixtime::serialize(
|
||||
QDateTime(QDate::currentDate().addDays(8), QTime(0, 0))) - 1;
|
||||
};
|
||||
|
||||
// At least half an hour later, at zero minutes/seconds.
|
||||
const auto schedule = QDateTime(
|
||||
now.date(),
|
||||
QTime(now.time().hour(), 0)
|
||||
).addSecs(60 * 60 * (now.time().minute() < 30 ? 1 : 2));
|
||||
|
||||
auto descriptor = Ui::ChooseDateTimeBox(box, {
|
||||
.title = tr::lng_group_call_schedule_title(),
|
||||
.submit = tr::lng_schedule_button(),
|
||||
.done = send,
|
||||
.min = min,
|
||||
.time = base::unixtime::serialize(schedule),
|
||||
.max = max,
|
||||
.description = std::move(description),
|
||||
});
|
||||
|
||||
using namespace rpl::mappers;
|
||||
*duration = rpl::combine(
|
||||
rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(base::timer_each(kLabelRefreshInterval)),
|
||||
std::move(descriptor.values) | rpl::filter(_1 != 0),
|
||||
_2
|
||||
) | rpl::map([](TimeId date) {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto duration = (date - now);
|
||||
if (duration >= 24 * 60 * 60) {
|
||||
return tr::lng_group_call_duration_days(
|
||||
tr::now,
|
||||
lt_count,
|
||||
duration / (24 * 60 * 60));
|
||||
} else if (duration >= 60 * 60) {
|
||||
return tr::lng_group_call_duration_hours(
|
||||
tr::now,
|
||||
lt_count,
|
||||
duration / (60 * 60));
|
||||
}
|
||||
return tr::lng_group_call_duration_minutes(
|
||||
tr::now,
|
||||
lt_count,
|
||||
std::max(duration / 60, 1));
|
||||
});
|
||||
}
|
||||
|
||||
void ChooseJoinAsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
Context context,
|
||||
@@ -124,12 +204,13 @@ void ChooseJoinAsBox(
|
||||
}
|
||||
Unexpected("Context in ChooseJoinAsBox.");
|
||||
}());
|
||||
const auto &labelSt = (context == Context::Switch)
|
||||
? st::groupCallJoinAsLabel
|
||||
: st::confirmPhoneAboutLabel;
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_group_call_join_as_about(),
|
||||
(context == Context::Switch
|
||||
? st::groupCallJoinAsLabel
|
||||
: st::confirmPhoneAboutLabel)));
|
||||
labelSt));
|
||||
|
||||
auto &lifetime = box->lifetime();
|
||||
const auto delegate = lifetime.make_state<
|
||||
@@ -155,6 +236,25 @@ void ChooseJoinAsBox(
|
||||
auto next = (context == Context::Switch)
|
||||
? tr::lng_settings_save()
|
||||
: tr::lng_continue();
|
||||
if (context == Context::Create) {
|
||||
const auto makeLink = [](const QString &text) {
|
||||
return Ui::Text::Link(text);
|
||||
};
|
||||
const auto label = box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_group_call_or_schedule(
|
||||
lt_link,
|
||||
tr::lng_group_call_schedule(makeLink),
|
||||
Ui::Text::WithEntities),
|
||||
labelSt));
|
||||
label->setClickHandlerFilter([=](const auto&...) {
|
||||
auto withJoinAs = info;
|
||||
withJoinAs.joinAs = controller->selected();
|
||||
box->getDelegate()->show(
|
||||
Box(ScheduleGroupCallBox, withJoinAs, done));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
box->addButton(std::move(next), [=] {
|
||||
auto copy = info;
|
||||
copy.joinAs = controller->selected();
|
||||
@@ -163,6 +263,37 @@ void ChooseJoinAsBox(
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities CreateOrJoinConfirmation(
|
||||
not_null<PeerData*> peer,
|
||||
ChooseJoinAsProcess::Context context,
|
||||
bool joinAsAlreadyUsed) {
|
||||
const auto existing = peer->groupCall();
|
||||
if (!existing) {
|
||||
return { peer->isBroadcast()
|
||||
? tr::lng_group_call_create_sure_channel(tr::now)
|
||||
: tr::lng_group_call_create_sure(tr::now) };
|
||||
}
|
||||
const auto channel = peer->asChannel();
|
||||
const auto anonymouseAdmin = channel
|
||||
&& ((channel->isMegagroup() && channel->amAnonymous())
|
||||
|| (channel->isBroadcast()
|
||||
&& (channel->amCreator()
|
||||
|| channel->hasAdminRights())));
|
||||
if (anonymouseAdmin && !joinAsAlreadyUsed) {
|
||||
return { tr::lng_group_call_join_sure_personal(tr::now) };
|
||||
} else if (context != ChooseJoinAsProcess::Context::JoinWithConfirm) {
|
||||
return {};
|
||||
}
|
||||
const auto name = !existing->title().isEmpty()
|
||||
? existing->title()
|
||||
: peer->name;
|
||||
return tr::lng_group_call_join_confirm(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChooseJoinAsProcess::~ChooseJoinAsProcess() {
|
||||
@@ -257,32 +388,52 @@ void ChooseJoinAsProcess::start(
|
||||
info.possibleJoinAs = std::move(list);
|
||||
|
||||
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
|
||||
&& (info.possibleJoinAs.front() == self)
|
||||
&& (!peer->isChannel()
|
||||
|| !peer->asChannel()->amAnonymous()
|
||||
|| (peer->isBroadcast() && !peer->canWrite()));
|
||||
&& (info.possibleJoinAs.front() == self);
|
||||
|
||||
// We already joined this voice chat, just rejoin with the same.
|
||||
const auto byAlreadyUsed = selectedId
|
||||
&& (info.joinAs->id == selectedId);
|
||||
&& (info.joinAs->id == selectedId)
|
||||
&& (peer->groupCall() != nullptr);
|
||||
|
||||
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
|
||||
if (context != Context::JoinWithConfirm) {
|
||||
auto confirmation = CreateOrJoinConfirmation(
|
||||
peer,
|
||||
context,
|
||||
byAlreadyUsed);
|
||||
if (confirmation.text.isEmpty()) {
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
const auto real = peer->groupCall();
|
||||
const auto name = (real && !real->title().isEmpty())
|
||||
? real->title()
|
||||
: peer->name;
|
||||
auto box = Box<::ConfirmBox>(
|
||||
tr::lng_group_call_join_confirm(
|
||||
const auto creating = !peer->groupCall();
|
||||
if (creating) {
|
||||
confirmation
|
||||
.append("\n\n")
|
||||
.append(tr::lng_group_call_or_schedule(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities),
|
||||
tr::lng_group_call_join(tr::now),
|
||||
crl::guard(&_request->guard, [=] { finish(info); }));
|
||||
lt_link,
|
||||
Ui::Text::Link(tr::lng_group_call_schedule(tr::now)),
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
const auto guard = base::make_weak(&_request->guard);
|
||||
const auto safeFinish = crl::guard(guard, [=] { finish(info); });
|
||||
const auto filter = [=](const auto &...) {
|
||||
if (guard) {
|
||||
_request->showBox(Box(
|
||||
ScheduleGroupCallBox,
|
||||
info,
|
||||
crl::guard(guard, finish)));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto box = ConfirmBox({
|
||||
.text = confirmation,
|
||||
.button = (creating
|
||||
? tr::lng_create_group_create()
|
||||
: tr::lng_group_call_join()),
|
||||
.callback = crl::guard(guard, [=] { finish(info); }),
|
||||
.st = &st::boxLabel,
|
||||
.filter = filter,
|
||||
});
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_request = nullptr;
|
||||
|
||||
@@ -112,7 +112,7 @@ private:
|
||||
}
|
||||
if (const auto chat = peer->asChat()) {
|
||||
return chat->admins.contains(user)
|
||||
|| (chat->creator == user->bareId());
|
||||
|| (chat->creator == peerToUser(user->id));
|
||||
} else if (const auto group = peer->asChannel()) {
|
||||
if (const auto mgInfo = group->mgInfo.get()) {
|
||||
if (mgInfo->creator == user) {
|
||||
@@ -184,6 +184,8 @@ GroupCall::GroupCall(
|
||||
, _joinAs(info.joinAs)
|
||||
, _possibleJoinAs(std::move(info.possibleJoinAs))
|
||||
, _joinHash(info.joinHash)
|
||||
, _id(inputCall.c_inputGroupCall().vid().v)
|
||||
, _scheduleDate(info.scheduleDate)
|
||||
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
||||
, _checkJoinedTimer([=] { checkJoined(); })
|
||||
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
||||
@@ -215,17 +217,35 @@ GroupCall::GroupCall(
|
||||
|
||||
checkGlobalShortcutAvailability();
|
||||
|
||||
const auto id = inputCall.c_inputGroupCall().vid().v;
|
||||
if (id) {
|
||||
if (const auto call = _peer->groupCall(); call && call->id() == id) {
|
||||
if (!_peer->canManageGroupCall() && call->joinMuted()) {
|
||||
_muted = MuteState::ForceMuted;
|
||||
}
|
||||
if (const auto real = lookupReal()) {
|
||||
subscribeToReal(real);
|
||||
if (!_peer->canManageGroupCall() && real->joinMuted()) {
|
||||
_muted = MuteState::ForceMuted;
|
||||
}
|
||||
_state = State::Joining;
|
||||
} else {
|
||||
_peer->session().changes().peerFlagsValue(
|
||||
_peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return lookupReal();
|
||||
}) | rpl::filter([](Data::GroupCall *real) {
|
||||
return real != nullptr;
|
||||
}) | rpl::map([](Data::GroupCall *real) {
|
||||
return not_null{ real };
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||
subscribeToReal(real);
|
||||
_realChanges.fire_copy(real);
|
||||
}, _lifetime);
|
||||
}
|
||||
if (_id) {
|
||||
join(inputCall);
|
||||
} else {
|
||||
start();
|
||||
start(info.scheduleDate);
|
||||
}
|
||||
if (_scheduleDate) {
|
||||
saveDefaultJoinAs(_joinAs);
|
||||
}
|
||||
|
||||
_mediaDevices->audioInputId(
|
||||
@@ -249,6 +269,21 @@ GroupCall::~GroupCall() {
|
||||
destroyController();
|
||||
}
|
||||
|
||||
void GroupCall::setScheduledDate(TimeId date) {
|
||||
const auto was = _scheduleDate;
|
||||
_scheduleDate = date;
|
||||
if (was && !date) {
|
||||
join(inputCall());
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::subscribeToReal(not_null<Data::GroupCall*> real) {
|
||||
real->scheduleDateValue(
|
||||
) | rpl::start_with_next([=](TimeId date) {
|
||||
setScheduledDate(date);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void GroupCall::checkGlobalShortcutAvailability() {
|
||||
auto &settings = Core::App().settings();
|
||||
if (!settings.groupCallPushToTalk()) {
|
||||
@@ -326,10 +361,33 @@ bool GroupCall::showChooseJoinAs() const {
|
||||
&& !_possibleJoinAs.front()->isSelf());
|
||||
}
|
||||
|
||||
void GroupCall::start() {
|
||||
bool GroupCall::scheduleStartSubscribed() const {
|
||||
if (const auto real = lookupReal()) {
|
||||
return real->scheduleStartSubscribed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Data::GroupCall *GroupCall::lookupReal() const {
|
||||
const auto real = _peer->groupCall();
|
||||
return (real && real->id() == _id) ? real : nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
|
||||
if (const auto real = lookupReal()) {
|
||||
return rpl::single(not_null{ real });
|
||||
}
|
||||
return _realChanges.events();
|
||||
}
|
||||
|
||||
void GroupCall::start(TimeId scheduleDate) {
|
||||
using Flag = MTPphone_CreateGroupCall::Flag;
|
||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag(0)),
|
||||
_peer->input,
|
||||
MTP_int(openssl::RandomValue<int32>())
|
||||
MTP_int(openssl::RandomValue<int32>()),
|
||||
MTPstring(), // title
|
||||
MTP_int(scheduleDate)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_acceptFields = true;
|
||||
_peer->session().api().applyUpdates(result);
|
||||
@@ -347,20 +405,16 @@ void GroupCall::start() {
|
||||
}
|
||||
|
||||
void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||
setState(State::Joining);
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCall(inputCall);
|
||||
} else if (const auto group = _peer->asChannel()) {
|
||||
group->setGroupCall(inputCall);
|
||||
} else {
|
||||
Unexpected("Peer type in GroupCall::join.");
|
||||
}
|
||||
|
||||
inputCall.match([&](const MTPDinputGroupCall &data) {
|
||||
_id = data.vid().v;
|
||||
_accessHash = data.vaccess_hash().v;
|
||||
rejoin();
|
||||
});
|
||||
setState(_scheduleDate ? State::Waiting : State::Joining);
|
||||
|
||||
if (_scheduleDate) {
|
||||
return;
|
||||
}
|
||||
rejoin();
|
||||
|
||||
using Update = Data::GroupCall::ParticipantUpdate;
|
||||
_peer->groupCall()->participantUpdated(
|
||||
@@ -409,6 +463,23 @@ void GroupCall::rejoinWithHash(const QString &hash) {
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::setJoinAs(not_null<PeerData*> as) {
|
||||
_joinAs = as;
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
channel->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::saveDefaultJoinAs(not_null<PeerData*> as) {
|
||||
setJoinAs(as);
|
||||
_api.request(MTPphone_SaveDefaultGroupCallJoinAs(
|
||||
_peer->input,
|
||||
_joinAs->input
|
||||
)).send();
|
||||
}
|
||||
|
||||
void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
if (state() != State::Joining
|
||||
&& state() != State::Joined
|
||||
@@ -424,12 +495,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
applyMeInCallLocally();
|
||||
LOG(("Call Info: Requesting join payload."));
|
||||
|
||||
_joinAs = as;
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
channel->setGroupCallDefaultJoinAs(_joinAs->id);
|
||||
}
|
||||
setJoinAs(as);
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
_instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) {
|
||||
@@ -471,6 +537,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
MTP_dataJSON(MTP_bytes(json))
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
_mySsrc = ssrc;
|
||||
_mySsrcs.emplace(ssrc);
|
||||
setState((_instanceState.current()
|
||||
== InstanceState::Disconnected)
|
||||
? State::Connecting
|
||||
@@ -478,6 +545,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
applyMeInCallLocally();
|
||||
maybeSendMutedUpdate(wasMuteState);
|
||||
_peer->session().api().applyUpdates(updates);
|
||||
applyQueuedSelfUpdates();
|
||||
checkFirstTimeJoined();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
@@ -560,7 +628,8 @@ void GroupCall::applyMeInCallLocally() {
|
||||
MTP_int(_mySsrc),
|
||||
MTP_int(volume),
|
||||
MTPstring(), // Don't update about text in local updates.
|
||||
MTP_long(raisedHandRating))),
|
||||
MTP_long(raisedHandRating),
|
||||
MTPDataJSON())),
|
||||
MTP_int(0)).c_updateGroupCallParticipants());
|
||||
}
|
||||
|
||||
@@ -605,7 +674,8 @@ void GroupCall::applyParticipantLocally(
|
||||
MTP_int(participant->ssrc),
|
||||
MTP_int(volume.value_or(participant->volume)),
|
||||
MTPstring(), // Don't update about text in local updates.
|
||||
MTP_long(participant->raisedHandRating))),
|
||||
MTP_long(participant->raisedHandRating),
|
||||
MTPDataJSON())),
|
||||
MTP_int(0)).c_updateGroupCallParticipants());
|
||||
}
|
||||
|
||||
@@ -640,8 +710,12 @@ void GroupCall::rejoinAs(Group::JoinInfo info) {
|
||||
.wasJoinAs = _joinAs,
|
||||
.nowJoinAs = info.joinAs,
|
||||
};
|
||||
setState(State::Joining);
|
||||
rejoin(info.joinAs);
|
||||
if (_scheduleDate) {
|
||||
saveDefaultJoinAs(info.joinAs);
|
||||
} else {
|
||||
setState(State::Joining);
|
||||
rejoin(info.joinAs);
|
||||
}
|
||||
_rejoinEvents.fire_copy(event);
|
||||
}
|
||||
|
||||
@@ -685,6 +759,29 @@ void GroupCall::finish(FinishType type) {
|
||||
})).send();
|
||||
}
|
||||
|
||||
void GroupCall::startScheduledNow() {
|
||||
if (!lookupReal()) {
|
||||
return;
|
||||
}
|
||||
_api.request(MTPphone_StartScheduledGroupCall(
|
||||
inputCall()
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::toggleScheduleStartSubscribed(bool subscribed) {
|
||||
if (!lookupReal()) {
|
||||
return;
|
||||
}
|
||||
_api.request(MTPphone_ToggleGroupCallStartSubscription(
|
||||
inputCall(),
|
||||
MTP_bool(subscribed)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GroupCall::setMuted(MuteState mute) {
|
||||
const auto set = [=] {
|
||||
const auto wasMuted = (muted() == MuteState::Muted)
|
||||
@@ -721,20 +818,31 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([&](const MTPDgroupCall &data) {
|
||||
handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const MTPDgroupCallDiscarded &data) {
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
handlePossibleDiscarded(data);
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
const MTPDgroupCall &data) {
|
||||
setScheduledDate(data.vschedule_date().value_or_empty());
|
||||
if (_acceptFields) {
|
||||
if (!_instance && !_id) {
|
||||
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
||||
const auto input = MTP_inputGroupCall(
|
||||
data.vid(),
|
||||
data.vaccess_hash());
|
||||
const auto scheduleDate = data.vschedule_date().value_or_empty();
|
||||
if (const auto chat = _peer->asChat()) {
|
||||
chat->setGroupCall(input, scheduleDate);
|
||||
} else if (const auto group = _peer->asChannel()) {
|
||||
group->setGroupCall(input, scheduleDate);
|
||||
} else {
|
||||
Unexpected("Peer type in GroupCall::join.");
|
||||
}
|
||||
join(input);
|
||||
}
|
||||
return;
|
||||
} else if (_id != data.vid().v
|
||||
|| _accessHash != data.vaccess_hash().v
|
||||
|| !_instance) {
|
||||
} else if (_id != data.vid().v || !_instance) {
|
||||
return;
|
||||
}
|
||||
const auto streamDcId = MTP::BareDcId(
|
||||
@@ -816,11 +924,17 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v == _id) {
|
||||
LOG(("Call Info: Hangup after groupCallDiscarded."));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::addParticipantsToInstance() {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real
|
||||
|| (real->id() != _id)
|
||||
|| (_instanceMode == InstanceMode::None)) {
|
||||
const auto real = lookupReal();
|
||||
if (!real || (_instanceMode == InstanceMode::None)) {
|
||||
return;
|
||||
}
|
||||
for (const auto &participant : real->participants()) {
|
||||
@@ -842,7 +956,7 @@ void GroupCall::addPreparedParticipants() {
|
||||
if (!_preparedParticipants.empty()) {
|
||||
_instance->addParticipants(base::take(_preparedParticipants));
|
||||
}
|
||||
if (const auto real = _peer->groupCall(); real && real->id() == _id) {
|
||||
if (const auto real = lookupReal()) {
|
||||
if (!_unresolvedSsrcs.empty()) {
|
||||
real->resolveParticipants(base::take(_unresolvedSsrcs));
|
||||
}
|
||||
@@ -870,10 +984,7 @@ void GroupCall::handleUpdate(const MTPUpdate &update) {
|
||||
void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) {
|
||||
data.vcall().match([](const MTPDgroupCall &) {
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
if (data.vid().v == _id) {
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
}
|
||||
handlePossibleDiscarded(data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -885,82 +996,105 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
|
||||
return;
|
||||
}
|
||||
const auto state = _state.current();
|
||||
if (state != State::Joined && state != State::Connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto handleOtherParticipants = [=](
|
||||
const MTPDgroupCallParticipant &data) {
|
||||
if (data.is_min()) {
|
||||
// No real information about mutedByMe or my custom volume.
|
||||
return;
|
||||
}
|
||||
const auto participantPeer = _peer->owner().peer(
|
||||
peerFromMTP(data.vpeer()));
|
||||
const auto participant = LookupParticipant(
|
||||
_peer,
|
||||
_id,
|
||||
participantPeer);
|
||||
if (!participant) {
|
||||
return;
|
||||
}
|
||||
_otherParticipantStateValue.fire(Group::ParticipantState{
|
||||
.peer = participantPeer,
|
||||
.volume = data.vvolume().value_or_empty(),
|
||||
.mutedByMe = data.is_muted_by_you(),
|
||||
});
|
||||
};
|
||||
|
||||
const auto joined = (state == State::Joined)
|
||||
|| (state == State::Connecting);
|
||||
for (const auto &participant : data.vparticipants().v) {
|
||||
participant.match([&](const MTPDgroupCallParticipant &data) {
|
||||
const auto isSelf = data.is_self()
|
||||
|| (data.is_min()
|
||||
&& peerFromMTP(data.vpeer()) == _joinAs->id);
|
||||
if (!isSelf) {
|
||||
handleOtherParticipants(data);
|
||||
return;
|
||||
}
|
||||
if (data.is_left()) {
|
||||
if (data.vsource().v == _mySsrc) {
|
||||
// I was removed from the call, rejoin.
|
||||
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
}
|
||||
return;
|
||||
} else if (data.vsource().v != _mySsrc) {
|
||||
// I joined from another device, hangup.
|
||||
LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
return;
|
||||
}
|
||||
if (data.is_muted() && !data.is_can_self_unmute()) {
|
||||
setMuted(data.vraise_hand_rating().value_or_empty()
|
||||
? MuteState::RaisedHand
|
||||
: MuteState::ForceMuted);
|
||||
} else if (_instanceMode == InstanceMode::Stream) {
|
||||
LOG(("Call Info: Rejoin after unforcemute in stream mode."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
} else if (muted() == MuteState::ForceMuted
|
||||
|| muted() == MuteState::RaisedHand) {
|
||||
setMuted(MuteState::Muted);
|
||||
if (!_instanceTransitioning) {
|
||||
notifyAboutAllowedToSpeak();
|
||||
}
|
||||
} else if (data.is_muted() && muted() != MuteState::Muted) {
|
||||
setMuted(MuteState::Muted);
|
||||
applyOtherParticipantUpdate(data);
|
||||
} else if (joined) {
|
||||
applySelfUpdate(data);
|
||||
} else {
|
||||
_queuedSelfUpdates.push_back(participant);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applyQueuedSelfUpdates() {
|
||||
const auto weak = base::make_weak(this);
|
||||
while (weak
|
||||
&& !_queuedSelfUpdates.empty()
|
||||
&& (_state.current() == State::Joined
|
||||
|| _state.current() == State::Connecting)) {
|
||||
const auto update = _queuedSelfUpdates.front();
|
||||
_queuedSelfUpdates.erase(_queuedSelfUpdates.begin());
|
||||
update.match([&](const MTPDgroupCallParticipant &data) {
|
||||
applySelfUpdate(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) {
|
||||
if (data.is_left()) {
|
||||
if (data.vsource().v == _mySsrc) {
|
||||
// I was removed from the call, rejoin.
|
||||
LOG(("Call Info: "
|
||||
"Rejoin after got 'left' with my ssrc."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
}
|
||||
return;
|
||||
} else if (data.vsource().v != _mySsrc) {
|
||||
if (!_mySsrcs.contains(data.vsource().v)) {
|
||||
// I joined from another device, hangup.
|
||||
LOG(("Call Info: "
|
||||
"Hangup after '!left' with ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
_mySsrc = 0;
|
||||
hangup();
|
||||
} else {
|
||||
LOG(("Call Info: "
|
||||
"Some old 'self' with '!left' and ssrc %1, my %2."
|
||||
).arg(data.vsource().v
|
||||
).arg(_mySsrc));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.is_muted() && !data.is_can_self_unmute()) {
|
||||
setMuted(data.vraise_hand_rating().value_or_empty()
|
||||
? MuteState::RaisedHand
|
||||
: MuteState::ForceMuted);
|
||||
} else if (_instanceMode == InstanceMode::Stream) {
|
||||
LOG(("Call Info: Rejoin after unforcemute in stream mode."));
|
||||
setState(State::Joining);
|
||||
rejoin();
|
||||
} else if (muted() == MuteState::ForceMuted
|
||||
|| muted() == MuteState::RaisedHand) {
|
||||
setMuted(MuteState::Muted);
|
||||
if (!_instanceTransitioning) {
|
||||
notifyAboutAllowedToSpeak();
|
||||
}
|
||||
} else if (data.is_muted() && muted() != MuteState::Muted) {
|
||||
setMuted(MuteState::Muted);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applyOtherParticipantUpdate(
|
||||
const MTPDgroupCallParticipant &data) {
|
||||
if (data.is_min()) {
|
||||
// No real information about mutedByMe or my custom volume.
|
||||
return;
|
||||
}
|
||||
const auto participantPeer = _peer->owner().peer(
|
||||
peerFromMTP(data.vpeer()));
|
||||
if (!LookupParticipant(_peer, _id, participantPeer)) {
|
||||
return;
|
||||
}
|
||||
_otherParticipantStateValue.fire(Group::ParticipantState{
|
||||
.peer = participantPeer,
|
||||
.volume = data.vvolume().value_or_empty(),
|
||||
.mutedByMe = data.is_muted_by_you(),
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::changeTitle(const QString &title) {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id || real->title() == title) {
|
||||
const auto real = lookupReal();
|
||||
if (!real || real->title() == title) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -975,8 +1109,8 @@ void GroupCall::changeTitle(const QString &title) {
|
||||
}
|
||||
|
||||
void GroupCall::toggleRecording(bool enabled, const QString &title) {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
const auto real = lookupReal();
|
||||
if (!real) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1121,7 +1255,8 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
|
||||
});
|
||||
});
|
||||
}).fail([=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q) {
|
||||
if (error.type() == u"GROUPCALL_JOIN_MISSING"_q
|
||||
|| error.type() == u"GROUPCALL_FORBIDDEN"_q) {
|
||||
for (const auto &[task, part] : _broadcastParts) {
|
||||
_api.request(part.requestId).cancel();
|
||||
}
|
||||
@@ -1129,7 +1264,8 @@ void GroupCall::broadcastPartStart(std::shared_ptr<LoadPartTask> task) {
|
||||
rejoin();
|
||||
return;
|
||||
}
|
||||
const auto status = MTP::IsFloodError(error)
|
||||
const auto status = (MTP::IsFloodError(error)
|
||||
|| error.type() == u"TIME_TOO_BIG"_q)
|
||||
? Status::NotReady
|
||||
: Status::ResyncNeeded;
|
||||
finish({
|
||||
@@ -1153,10 +1289,8 @@ void GroupCall::broadcastPartCancel(not_null<LoadPartTask*> task) {
|
||||
|
||||
void GroupCall::requestParticipantsInformation(
|
||||
const std::vector<uint32_t> &ssrcs) {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real
|
||||
|| (real->id() != _id)
|
||||
|| (_instanceMode == InstanceMode::None)) {
|
||||
const auto real = lookupReal();
|
||||
if (!real || (_instanceMode == InstanceMode::None)) {
|
||||
for (const auto ssrc : ssrcs) {
|
||||
_unresolvedSsrcs.emplace(ssrc);
|
||||
}
|
||||
@@ -1190,8 +1324,8 @@ void GroupCall::updateInstanceMuteState() {
|
||||
}
|
||||
|
||||
void GroupCall::updateInstanceVolumes() {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
const auto real = lookupReal();
|
||||
if (!real) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1267,8 +1401,8 @@ void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
|
||||
}
|
||||
|
||||
void GroupCall::checkLastSpoke() {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
const auto real = lookupReal();
|
||||
if (!real) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1479,8 +1613,8 @@ void GroupCall::editParticipant(
|
||||
|
||||
std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &users) {
|
||||
const auto real = _peer->groupCall();
|
||||
if (!real || real->id() != _id) {
|
||||
const auto real = lookupReal();
|
||||
if (!real) {
|
||||
return 0;
|
||||
}
|
||||
const auto owner = &_peer->owner();
|
||||
|
||||
@@ -34,6 +34,7 @@ class MediaDevices;
|
||||
namespace Data {
|
||||
struct LastSpokeTimes;
|
||||
struct GroupCallParticipant;
|
||||
class GroupCall;
|
||||
} // namespace Data
|
||||
|
||||
namespace Calls {
|
||||
@@ -109,8 +110,15 @@ public:
|
||||
return _joinAs;
|
||||
}
|
||||
[[nodiscard]] bool showChooseJoinAs() const;
|
||||
[[nodiscard]] TimeId scheduleDate() const {
|
||||
return _scheduleDate;
|
||||
}
|
||||
[[nodiscard]] bool scheduleStartSubscribed() const;
|
||||
|
||||
void start();
|
||||
[[nodiscard]] Data::GroupCall *lookupReal() const;
|
||||
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
|
||||
|
||||
void start(TimeId scheduleDate);
|
||||
void hangup();
|
||||
void discard();
|
||||
void rejoinAs(Group::JoinInfo info);
|
||||
@@ -123,6 +131,8 @@ public:
|
||||
[[nodiscard]] bool recordingStoppedByMe() const {
|
||||
return _recordingStoppedByMe;
|
||||
}
|
||||
void startScheduledNow();
|
||||
void toggleScheduleStartSubscribed(bool subscribed);
|
||||
|
||||
void setMuted(MuteState mute);
|
||||
void setMutedAndUpdate(MuteState mute);
|
||||
@@ -138,6 +148,7 @@ public:
|
||||
|
||||
enum State {
|
||||
Creating,
|
||||
Waiting,
|
||||
Joining,
|
||||
Connecting,
|
||||
Joined,
|
||||
@@ -228,6 +239,7 @@ private:
|
||||
};
|
||||
|
||||
void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data);
|
||||
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
|
||||
void handleUpdate(const MTPDupdateGroupCall &data);
|
||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||
void handleRequestError(const MTP::Error &error);
|
||||
@@ -244,6 +256,10 @@ private:
|
||||
void applyMeInCallLocally();
|
||||
void rejoin();
|
||||
void rejoin(not_null<PeerData*> as);
|
||||
void setJoinAs(not_null<PeerData*> as);
|
||||
void saveDefaultJoinAs(not_null<PeerData*> as);
|
||||
void subscribeToReal(not_null<Data::GroupCall*> real);
|
||||
void setScheduledDate(TimeId date);
|
||||
|
||||
void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
|
||||
void setInstanceConnected(tgcalls::GroupNetworkState networkState);
|
||||
@@ -275,6 +291,9 @@ private:
|
||||
not_null<PeerData*> participantPeer,
|
||||
bool mute,
|
||||
std::optional<int> volume);
|
||||
void applyQueuedSelfUpdates();
|
||||
void applySelfUpdate(const MTPDgroupCallParticipant &data);
|
||||
void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data);
|
||||
|
||||
[[nodiscard]] MTPInputGroupCall inputCall() const;
|
||||
|
||||
@@ -283,6 +302,7 @@ private:
|
||||
rpl::event_stream<PeerData*> _peerStream;
|
||||
not_null<History*> _history; // Can change in legacy group migration.
|
||||
MTP::Sender _api;
|
||||
rpl::event_stream<not_null<Data::GroupCall*>> _realChanges;
|
||||
rpl::variable<State> _state = State::Creating;
|
||||
rpl::variable<InstanceState> _instanceState
|
||||
= InstanceState::Disconnected;
|
||||
@@ -305,10 +325,13 @@ private:
|
||||
bool _acceptFields = false;
|
||||
|
||||
rpl::event_stream<Group::ParticipantState> _otherParticipantStateValue;
|
||||
std::vector<MTPGroupCallParticipant> _queuedSelfUpdates;
|
||||
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint32 _mySsrc = 0;
|
||||
TimeId _scheduleDate = 0;
|
||||
base::flat_set<uint32> _mySsrcs;
|
||||
mtpRequestId _createRequestId = 0;
|
||||
mtpRequestId _updateMuteRequestId = 0;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ struct JoinInfo {
|
||||
not_null<PeerData*> joinAs;
|
||||
std::vector<not_null<PeerData*>> possibleJoinAs;
|
||||
QString joinHash;
|
||||
TimeId scheduleDate = 0;
|
||||
};
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "calls/calls_group_call.h"
|
||||
#include "calls/calls_group_common.h"
|
||||
#include "calls/calls_group_menu.h"
|
||||
#include "calls/calls_volume_item.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -143,9 +144,6 @@ public:
|
||||
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
||||
void stopLastActionRipple() override;
|
||||
|
||||
int nameIconWidth() const override {
|
||||
return 0;
|
||||
}
|
||||
QSize actionSize() const override {
|
||||
return QSize(
|
||||
st::groupCallActiveButton.width,
|
||||
@@ -207,17 +205,17 @@ private:
|
||||
};
|
||||
|
||||
struct StatusIcon {
|
||||
StatusIcon(float volume)
|
||||
: speaker(st::groupCallStatusSpeakerIcon)
|
||||
, arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
|
||||
st::groupCallStatusSpeakerArcsAnimation,
|
||||
kSpeakerThreshold,
|
||||
volume,
|
||||
Ui::Paint::ArcsAnimation::Direction::Right)) {
|
||||
}
|
||||
StatusIcon(bool shown, float volume);
|
||||
|
||||
const style::icon &speaker;
|
||||
const std::unique_ptr<Ui::Paint::ArcsAnimation> arcs;
|
||||
Ui::Paint::ArcsAnimation arcs;
|
||||
Ui::Animations::Simple arcsAnimation;
|
||||
Ui::Animations::Simple shownAnimation;
|
||||
QString percent;
|
||||
int percentWidth = 0;
|
||||
int arcsWidth = 0;
|
||||
int wasArcsWidth = 0;
|
||||
bool shown = true;
|
||||
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
@@ -249,7 +247,6 @@ private:
|
||||
Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon.
|
||||
Ui::Animations::Simple _mutedAnimation; // For gray/red icon.
|
||||
Ui::Animations::Simple _activeAnimation; // For icon cross animation.
|
||||
Ui::Animations::Simple _arcsAnimation; // For volume arcs animation.
|
||||
QString _aboutText;
|
||||
crl::time _speakingLastTime = 0;
|
||||
uint64 _raisedHandRating = 0;
|
||||
@@ -320,7 +317,7 @@ private:
|
||||
not_null<PeerData*> participantPeer,
|
||||
bool participantIsCallAdmin,
|
||||
not_null<Row*> row);
|
||||
void setupListChangeViewers(not_null<GroupCall*> call);
|
||||
void setupListChangeViewers();
|
||||
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
||||
void updateRow(
|
||||
const std::optional<Data::GroupCall::Participant> &was,
|
||||
@@ -338,16 +335,11 @@ private:
|
||||
uint64 raiseHandRating) const;
|
||||
Row *findRow(not_null<PeerData*> participantPeer) const;
|
||||
|
||||
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
|
||||
void appendInvitedUsers();
|
||||
void scheduleRaisedHandStatusRemove();
|
||||
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
const not_null<GroupCall*> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
// Use only resolvedRealCall() method, not this value directly.
|
||||
Data::GroupCall *_realCallRawValue = nullptr;
|
||||
uint64 _realId = 0;
|
||||
bool _prepared = false;
|
||||
|
||||
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
||||
@@ -375,6 +367,26 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QString StatusPercentString(float volume) {
|
||||
return QString::number(int(std::round(volume * 200))) + '%';
|
||||
}
|
||||
|
||||
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
|
||||
return st::normalFont->width(percent);
|
||||
}
|
||||
|
||||
Row::StatusIcon::StatusIcon(bool shown, float volume)
|
||||
: speaker(st::groupCallStatusSpeakerIcon)
|
||||
, arcs(
|
||||
st::groupCallStatusSpeakerArcsAnimation,
|
||||
kSpeakerThreshold,
|
||||
volume,
|
||||
Ui::Paint::ArcsAnimation::Direction::Right)
|
||||
, percent(StatusPercentString(volume))
|
||||
, percentWidth(StatusPercentWidth(percent))
|
||||
, shown(shown) {
|
||||
}
|
||||
|
||||
Row::Row(
|
||||
not_null<RowDelegate*> delegate,
|
||||
not_null<PeerData*> participantPeer)
|
||||
@@ -435,32 +447,30 @@ void Row::setSpeaking(bool speaking) {
|
||||
|| (_state == State::MutedByMe)
|
||||
|| (_state == State::Muted)
|
||||
|| (_state == State::RaisedHand)) {
|
||||
_statusIcon = nullptr;
|
||||
if (_statusIcon) {
|
||||
_statusIcon = nullptr;
|
||||
_delegate->rowUpdateRow(this);
|
||||
}
|
||||
} else if (!_statusIcon) {
|
||||
_statusIcon = std::make_unique<StatusIcon>(
|
||||
(_volume != Group::kDefaultVolume),
|
||||
(float)_volume / Group::kMaxVolume);
|
||||
_statusIcon->arcs->setStrokeRatio(kArcsStrokeRatio);
|
||||
_statusIcon->arcsWidth = _statusIcon->arcs->finishedWidth();
|
||||
|
||||
const auto wasArcsWidth = _statusIcon->lifetime.make_state<int>(0);
|
||||
|
||||
_statusIcon->arcs->startUpdateRequests(
|
||||
_statusIcon->arcs.setStrokeRatio(kArcsStrokeRatio);
|
||||
_statusIcon->arcsWidth = _statusIcon->arcs.finishedWidth();
|
||||
_statusIcon->arcs.startUpdateRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!_arcsAnimation.animating()) {
|
||||
*wasArcsWidth = _statusIcon->arcsWidth;
|
||||
if (!_statusIcon->arcsAnimation.animating()) {
|
||||
_statusIcon->wasArcsWidth = _statusIcon->arcsWidth;
|
||||
}
|
||||
auto callback = [=](float64 value) {
|
||||
if (_statusIcon) {
|
||||
_statusIcon->arcs->update(crl::now());
|
||||
|
||||
_statusIcon->arcsWidth = anim::interpolate(
|
||||
*wasArcsWidth,
|
||||
_statusIcon->arcs->finishedWidth(),
|
||||
value);
|
||||
}
|
||||
_statusIcon->arcs.update(crl::now());
|
||||
_statusIcon->arcsWidth = anim::interpolate(
|
||||
_statusIcon->wasArcsWidth,
|
||||
_statusIcon->arcs.finishedWidth(),
|
||||
value);
|
||||
_delegate->rowUpdateRow(this);
|
||||
};
|
||||
_arcsAnimation.start(
|
||||
_statusIcon->arcsAnimation.start(
|
||||
std::move(callback),
|
||||
0.,
|
||||
1.,
|
||||
@@ -535,7 +545,20 @@ void Row::setSsrc(uint32 ssrc) {
|
||||
void Row::setVolume(int volume) {
|
||||
_volume = volume;
|
||||
if (_statusIcon) {
|
||||
_statusIcon->arcs->setValue((float)volume / Group::kMaxVolume);
|
||||
const auto floatVolume = (float)volume / Group::kMaxVolume;
|
||||
_statusIcon->arcs.setValue(floatVolume);
|
||||
_statusIcon->percent = StatusPercentString(floatVolume);
|
||||
_statusIcon->percentWidth = StatusPercentWidth(_statusIcon->percent);
|
||||
|
||||
const auto shown = (volume != Group::kDefaultVolume);
|
||||
if (_statusIcon->shown != shown) {
|
||||
_statusIcon->shown = shown;
|
||||
_statusIcon->shownAnimation.start(
|
||||
[=] { _delegate->rowUpdateRow(this); },
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
st::groupCallSpeakerArcsAnimation.duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,21 +683,20 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback {
|
||||
}
|
||||
|
||||
int Row::statusIconWidth() const {
|
||||
if (!_statusIcon) {
|
||||
if (!_statusIcon || !_speaking) {
|
||||
return 0;
|
||||
}
|
||||
return _speaking
|
||||
? (_statusIcon->speaker.width() + _statusIcon->arcsWidth)
|
||||
: 0;
|
||||
const auto shown = _statusIcon->shownAnimation.value(
|
||||
_statusIcon->shown ? 1. : 0.);
|
||||
const auto full = _statusIcon->speaker.width()
|
||||
+ _statusIcon->arcsWidth
|
||||
+ _statusIcon->percentWidth
|
||||
+ st::normalFont->spacew;
|
||||
return int(std::round(shown * full));
|
||||
}
|
||||
|
||||
int Row::statusIconHeight() const {
|
||||
if (!_statusIcon) {
|
||||
return 0;
|
||||
}
|
||||
return _speaking
|
||||
? _statusIcon->speaker.height()
|
||||
: 0;
|
||||
return (_statusIcon && _speaking) ? _statusIcon->speaker.height() : 0;
|
||||
}
|
||||
|
||||
void Row::paintStatusIcon(
|
||||
@@ -685,6 +707,12 @@ void Row::paintStatusIcon(
|
||||
if (!_statusIcon) {
|
||||
return;
|
||||
}
|
||||
const auto shown = _statusIcon->shownAnimation.value(
|
||||
_statusIcon->shown ? 1. : 0.);
|
||||
if (shown == 0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.setFont(font);
|
||||
const auto color = (_speaking
|
||||
? st.statusFgActive
|
||||
@@ -699,17 +727,34 @@ void Row::paintStatusIcon(
|
||||
+ QPoint(
|
||||
speakerRect.width() - st::groupCallStatusSpeakerArcsSkip,
|
||||
speakerRect.height() / 2);
|
||||
const auto fullWidth = speakerRect.width()
|
||||
+ _statusIcon->arcsWidth
|
||||
+ _statusIcon->percentWidth
|
||||
+ st::normalFont->spacew;
|
||||
|
||||
const auto volume = std::round(_volume / 100.);
|
||||
p.save();
|
||||
if (shown < 1.) {
|
||||
const auto centerx = speakerRect.x() + fullWidth / 2;
|
||||
const auto centery = speakerRect.y() + speakerRect.height() / 2;
|
||||
p.translate(centerx, centery);
|
||||
p.scale(shown, shown);
|
||||
p.translate(-centerx, -centery);
|
||||
}
|
||||
_statusIcon->speaker.paint(
|
||||
p,
|
||||
speakerRect.topLeft(),
|
||||
speakerRect.width(),
|
||||
color);
|
||||
|
||||
p.save();
|
||||
p.translate(arcPosition);
|
||||
_statusIcon->arcs->paint(p, color);
|
||||
_statusIcon->arcs.paint(p, color);
|
||||
p.translate(-arcPosition);
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st.statusFgActive);
|
||||
p.drawTextLeft(
|
||||
st.statusPosition.x() + speakerRect.width() + _statusIcon->arcsWidth,
|
||||
st.statusPosition.y(),
|
||||
fullWidth,
|
||||
_statusIcon->percent);
|
||||
p.restore();
|
||||
}
|
||||
|
||||
@@ -738,11 +783,14 @@ void Row::paintStatusText(
|
||||
if (about.isEmpty()
|
||||
&& _state != State::Invited
|
||||
&& _state != State::MutedByMe) {
|
||||
p.save();
|
||||
paintStatusIcon(p, st, font, selected);
|
||||
|
||||
const auto translatedWidth = statusIconWidth();
|
||||
p.translate(translatedWidth, 0);
|
||||
const auto guard = gsl::finally([&] { p.restore(); });
|
||||
const auto guard = gsl::finally([&] {
|
||||
p.translate(-translatedWidth, 0);
|
||||
});
|
||||
|
||||
PeerListRow::paintStatusText(
|
||||
p,
|
||||
st,
|
||||
@@ -821,9 +869,7 @@ void Row::paintAction(
|
||||
void Row::refreshStatus() {
|
||||
setCustomStatus(
|
||||
(_speaking
|
||||
? u"%1% %2"_q
|
||||
.arg(std::round(_volume / 100.))
|
||||
.arg(tr::lng_group_call_active(tr::now))
|
||||
? tr::lng_group_call_active(tr::now)
|
||||
: _raisedHandStatus
|
||||
? tr::lng_group_call_raised_hand_status(tr::now)
|
||||
: tr::lng_group_call_inactive(tr::now)),
|
||||
@@ -858,7 +904,7 @@ MembersController::MembersController(
|
||||
, _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
|
||||
, _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
|
||||
, _coloredCrossLine(st::groupCallMemberColoredCrossLine) {
|
||||
setupListChangeViewers(call);
|
||||
setupListChangeViewers();
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -913,32 +959,20 @@ MembersController::~MembersController() {
|
||||
base::take(_menu);
|
||||
}
|
||||
|
||||
void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
const auto peer = call->peer();
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return peer->groupCall();
|
||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||
const auto call = _call.get();
|
||||
return call && real && (real->id() == call->id());
|
||||
}) | rpl::take(
|
||||
1
|
||||
void MembersController::setupListChangeViewers() {
|
||||
_call->real(
|
||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||
subscribeToChanges(real);
|
||||
}, _lifetime);
|
||||
|
||||
call->stateValue(
|
||||
_call->stateValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto call = _call.get();
|
||||
const auto real = peer->groupCall();
|
||||
if (call && real && (real->id() == call->id())) {
|
||||
if (const auto real = _call->lookupReal()) {
|
||||
//updateRow(channel->session().user());
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
call->levelUpdates(
|
||||
_call->levelUpdates(
|
||||
) | rpl::start_with_next([=](const LevelUpdate &update) {
|
||||
const auto i = _soundingRowBySsrc.find(update.ssrc);
|
||||
if (i != end(_soundingRowBySsrc)) {
|
||||
@@ -946,7 +980,7 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
call->rejoinEvents(
|
||||
_call->rejoinEvents(
|
||||
) | rpl::start_with_next([=](const Group::RejoinEvent &event) {
|
||||
const auto guard = gsl::finally([&] {
|
||||
delegate()->peerListRefreshRows();
|
||||
@@ -963,9 +997,6 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
|
||||
}
|
||||
|
||||
void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_realCallRawValue = real;
|
||||
_realId = real->id();
|
||||
|
||||
_fullCount = real->fullCountValue();
|
||||
|
||||
real->participantsSliceAdded(
|
||||
@@ -1002,17 +1033,19 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
}
|
||||
|
||||
void MembersController::appendInvitedUsers() {
|
||||
for (const auto user : _peer->owner().invitedToCallUsers(_realId)) {
|
||||
if (auto row = createInvitedRow(user)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (const auto id = _call->id()) {
|
||||
for (const auto user : _peer->owner().invitedToCallUsers(id)) {
|
||||
if (auto row = createInvitedRow(user)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
|
||||
using Invite = Data::Session::InviteToCall;
|
||||
_peer->owner().invitesToCalls(
|
||||
) | rpl::filter([=](const Invite &invite) {
|
||||
return (invite.id == _realId);
|
||||
return (invite.id == _call->id());
|
||||
}) | rpl::start_with_next([=](const Invite &invite) {
|
||||
if (auto row = createInvitedRow(invite.user)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
@@ -1069,7 +1102,7 @@ void MembersController::updateRow(
|
||||
if (checkPosition) {
|
||||
checkRowPosition(checkPosition);
|
||||
} else if (addedToBottom) {
|
||||
const auto real = resolvedRealCall();
|
||||
const auto real = _call->lookupReal();
|
||||
if (real && real->joinedToTop()) {
|
||||
const auto proj = [&](const PeerListRow &other) {
|
||||
const auto &real = static_cast<const Row&>(other);
|
||||
@@ -1260,15 +1293,7 @@ void MembersController::updateRowLevel(
|
||||
|
||||
Row *MembersController::findRow(not_null<PeerData*> participantPeer) const {
|
||||
return static_cast<Row*>(
|
||||
delegate()->peerListFindRow(participantPeer->id));
|
||||
}
|
||||
|
||||
Data::GroupCall *MembersController::resolvedRealCall() const {
|
||||
return (_realCallRawValue
|
||||
&& (_peer->groupCall() == _realCallRawValue)
|
||||
&& (_realCallRawValue->id() == _realId))
|
||||
? _realCallRawValue
|
||||
: nullptr;
|
||||
delegate()->peerListFindRow(participantPeer->id.value));
|
||||
}
|
||||
|
||||
Main::Session &MembersController::session() const {
|
||||
@@ -1281,9 +1306,7 @@ void MembersController::prepare() {
|
||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||
setSearchNoResultsText(tr::lng_blocked_list_not_found(tr::now));
|
||||
|
||||
const auto call = _call.get();
|
||||
if (const auto real = _peer->groupCall()
|
||||
; real && call && real->id() == call->id()) {
|
||||
if (const auto real = _call->lookupReal()) {
|
||||
prepareRows(real);
|
||||
} else if (auto row = createRowForMe()) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
@@ -1291,15 +1314,12 @@ void MembersController::prepare() {
|
||||
}
|
||||
|
||||
loadMoreRows();
|
||||
if (_realId) {
|
||||
appendInvitedUsers();
|
||||
}
|
||||
appendInvitedUsers();
|
||||
_prepared = true;
|
||||
}
|
||||
|
||||
bool MembersController::isMe(not_null<PeerData*> participantPeer) const {
|
||||
const auto call = _call.get();
|
||||
return call && (call->joinAs() == participantPeer);
|
||||
return (_call->joinAs() == participantPeer);
|
||||
}
|
||||
|
||||
void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
@@ -1328,19 +1348,17 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
}
|
||||
}
|
||||
if (!foundMe) {
|
||||
if (const auto call = _call.get()) {
|
||||
const auto me = call->joinAs();
|
||||
const auto i = ranges::find(
|
||||
participants,
|
||||
me,
|
||||
&Data::GroupCall::Participant::peer);
|
||||
auto row = (i != end(participants))
|
||||
? createRow(*i)
|
||||
: createRowForMe();
|
||||
if (row) {
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
const auto me = _call->joinAs();
|
||||
const auto i = ranges::find(
|
||||
participants,
|
||||
me,
|
||||
&Data::GroupCall::Participant::peer);
|
||||
auto row = (i != end(participants))
|
||||
? createRow(*i)
|
||||
: createRowForMe();
|
||||
if (row) {
|
||||
changed = true;
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
}
|
||||
for (const auto &participant : participants) {
|
||||
@@ -1355,7 +1373,7 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
|
||||
}
|
||||
|
||||
void MembersController::loadMoreRows() {
|
||||
if (const auto real = _peer->groupCall()) {
|
||||
if (const auto real = _call->lookupReal()) {
|
||||
real->requestParticipants();
|
||||
}
|
||||
}
|
||||
@@ -1384,7 +1402,7 @@ void MembersController::rowUpdateRow(not_null<Row*> row) {
|
||||
|
||||
void MembersController::rowScheduleRaisedHandStatusRemove(
|
||||
not_null<Row*> row) {
|
||||
const auto id = row->peer()->id;
|
||||
const auto id = row->id();
|
||||
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
|
||||
const auto i = _raisedHandStatusRemoveAt.find(id);
|
||||
if (i != _raisedHandStatusRemoveAt.end()) {
|
||||
@@ -1573,7 +1591,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
Window::SectionShow::Way::Forward);
|
||||
});
|
||||
};
|
||||
const auto removeFromGroup = crl::guard(this, [=] {
|
||||
const auto removeFromVoiceChat = crl::guard(this, [=] {
|
||||
_kickParticipantRequests.fire_copy(participantPeer);
|
||||
});
|
||||
|
||||
@@ -1583,12 +1601,10 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
}
|
||||
|
||||
if (isMe(participantPeer)) {
|
||||
if (const auto strong = _call.get()
|
||||
; strong && strong->muted() == MuteState::RaisedHand) {
|
||||
if (_call->muted() == MuteState::RaisedHand) {
|
||||
const auto removeHand = [=] {
|
||||
if (const auto strong = _call.get()
|
||||
; strong && strong->muted() == MuteState::RaisedHand) {
|
||||
strong->setMutedAndUpdate(MuteState::ForceMuted);
|
||||
if (_call->muted() == MuteState::RaisedHand) {
|
||||
_call->setMutedAndUpdate(MuteState::ForceMuted);
|
||||
}
|
||||
};
|
||||
result->addAction(
|
||||
@@ -1610,9 +1626,7 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
}
|
||||
const auto canKick = [&] {
|
||||
const auto user = participantPeer->asUser();
|
||||
if (!user) {
|
||||
return false;
|
||||
} else if (static_cast<Row*>(row.get())->state()
|
||||
if (static_cast<Row*>(row.get())->state()
|
||||
== Row::State::Invited) {
|
||||
return false;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
@@ -1620,16 +1634,16 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
|
||||
|| (user
|
||||
&& chat->canBanMembers()
|
||||
&& !chat->admins.contains(user));
|
||||
} else if (const auto group = _peer->asMegagroup()) {
|
||||
return group->amCreator()
|
||||
|| (user && group->canRestrictUser(user));
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
return channel->canRestrictParticipant(participantPeer);
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
if (canKick) {
|
||||
result->addAction(
|
||||
tr::lng_context_remove_from_group(tr::now),
|
||||
removeFromGroup);
|
||||
result->addAction(MakeAttentionAction(
|
||||
result->menu(),
|
||||
tr::lng_group_call_context_remove(tr::now),
|
||||
removeFromVoiceChat));
|
||||
}
|
||||
}
|
||||
if (result->empty()) {
|
||||
@@ -1679,14 +1693,12 @@ void MembersController::addMuteActionsToContextMenu(
|
||||
|
||||
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
|
||||
|
||||
const auto call = _call.get();
|
||||
if (!isMuted || (call && call->joinAs() == participantPeer)) {
|
||||
auto otherParticipantStateValue = call
|
||||
? call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
return data.peer == participantPeer;
|
||||
})
|
||||
: rpl::never<Group::ParticipantState>() | rpl::type_erased();
|
||||
if (!isMuted || _call->joinAs() == participantPeer) {
|
||||
auto otherParticipantStateValue
|
||||
= _call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
return data.peer == participantPeer;
|
||||
});
|
||||
|
||||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
@@ -1765,11 +1777,7 @@ void MembersController::addMuteActionsToContextMenu(
|
||||
}
|
||||
|
||||
std::unique_ptr<Row> MembersController::createRowForMe() {
|
||||
const auto call = _call.get();
|
||||
if (!call) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(this, call->joinAs());
|
||||
auto result = std::make_unique<Row>(this, _call->joinAs());
|
||||
updateRow(result.get(), nullptr);
|
||||
return result;
|
||||
}
|
||||
@@ -1828,12 +1836,8 @@ auto Members::kickParticipantRequests() const
|
||||
int Members::desiredHeight() const {
|
||||
const auto top = _addMember ? _addMember->height() : 0;
|
||||
auto count = [&] {
|
||||
if (const auto call = _call.get()) {
|
||||
if (const auto real = call->peer()->groupCall()) {
|
||||
if (call->id() == real->id()) {
|
||||
return real->fullCount();
|
||||
}
|
||||
}
|
||||
if (const auto real = _call->lookupReal()) {
|
||||
return real->fullCount();
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
@@ -1862,16 +1866,7 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
_canAddMembers = rpl::single(
|
||||
false
|
||||
) | rpl::then(peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::GroupCall
|
||||
) | rpl::map([=] {
|
||||
return peer->groupCall();
|
||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||
const auto call = _call.get();
|
||||
return call && real && (real->id() == call->id());
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::then(_call->real(
|
||||
) | rpl::map([=] {
|
||||
return Data::PeerFlagValue(
|
||||
channel,
|
||||
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
|
||||
void updateControlsGeometry();
|
||||
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
const not_null<GroupCall*> _call;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
std::unique_ptr<PeerListController> _listController;
|
||||
object_ptr<Ui::SettingsButton> _addMember = { nullptr };
|
||||
|
||||
@@ -514,21 +514,6 @@ base::unique_qptr<Ui::Menu::ItemBase> MakeRecordingAction(
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::Menu::ItemBase> MakeFinishAction(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
Fn<void()> callback) {
|
||||
return base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
st::groupCallFinishMenu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu,
|
||||
tr::lng_group_call_end(tr::now),
|
||||
std::move(callback)),
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void LeaveBox(
|
||||
@@ -536,16 +521,25 @@ void LeaveBox(
|
||||
not_null<GroupCall*> call,
|
||||
bool discardChecked,
|
||||
BoxContext context) {
|
||||
box->setTitle(tr::lng_group_call_leave_title());
|
||||
const auto scheduled = (call->scheduleDate() != 0);
|
||||
if (!scheduled) {
|
||||
box->setTitle(tr::lng_group_call_leave_title());
|
||||
}
|
||||
const auto inCall = (context == BoxContext::GroupCallPanel);
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
tr::lng_group_call_leave_sure(),
|
||||
(inCall ? st::groupCallBoxLabel : st::boxLabel)));
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
(scheduled
|
||||
? tr::lng_group_call_close_sure()
|
||||
: tr::lng_group_call_leave_sure()),
|
||||
(inCall ? st::groupCallBoxLabel : st::boxLabel)),
|
||||
scheduled ? st::boxPadding : st::boxRowPadding);
|
||||
const auto discard = call->peer()->canManageGroupCall()
|
||||
? box->addRow(object_ptr<Ui::Checkbox>(
|
||||
box.get(),
|
||||
tr::lng_group_call_end(),
|
||||
(scheduled
|
||||
? tr::lng_group_call_also_cancel()
|
||||
: tr::lng_group_call_also_end()),
|
||||
discardChecked,
|
||||
(inCall ? st::groupCallCheckbox : st::defaultBoxCheckbox),
|
||||
(inCall ? st::groupCallCheck : st::defaultCheck)),
|
||||
@@ -556,7 +550,10 @@ void LeaveBox(
|
||||
st::boxRowPadding.bottom()))
|
||||
: nullptr;
|
||||
const auto weak = base::make_weak(call.get());
|
||||
box->addButton(tr::lng_group_call_leave(), [=] {
|
||||
auto label = scheduled
|
||||
? tr::lng_group_call_close()
|
||||
: tr::lng_group_call_leave();
|
||||
box->addButton(std::move(label), [=] {
|
||||
const auto discardCall = (discard && discard->checked());
|
||||
box->closeBox();
|
||||
|
||||
@@ -571,19 +568,20 @@ void LeaveBox(
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
void ConfirmBox(
|
||||
void ConfirmBoxBuilder(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const TextWithEntities &text,
|
||||
rpl::producer<QString> button,
|
||||
Fn<void()> callback) {
|
||||
box->addRow(
|
||||
ConfirmBoxArgs &&args) {
|
||||
const auto label = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
rpl::single(text),
|
||||
st::groupCallBoxLabel),
|
||||
rpl::single(args.text),
|
||||
args.st ? *args.st : st::groupCallBoxLabel),
|
||||
st::boxPadding);
|
||||
if (callback) {
|
||||
box->addButton(std::move(button), callback);
|
||||
if (args.callback) {
|
||||
box->addButton(std::move(args.button), std::move(args.callback));
|
||||
}
|
||||
if (args.filter) {
|
||||
label->setClickHandlerFilter(std::move(args.filter));
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
@@ -609,7 +607,8 @@ void FillMenu(
|
||||
|
||||
const auto addEditJoinAs = call->showChooseJoinAs();
|
||||
const auto addEditTitle = peer->canManageGroupCall();
|
||||
const auto addEditRecording = peer->canManageGroupCall();
|
||||
const auto addEditRecording = peer->canManageGroupCall()
|
||||
&& !real->scheduleDate();
|
||||
if (addEditJoinAs) {
|
||||
menu->addAction(MakeJoinAsAction(
|
||||
menu->menu(),
|
||||
@@ -666,7 +665,7 @@ void FillMenu(
|
||||
showBox(Box(SettingsBox, strong));
|
||||
}
|
||||
});
|
||||
menu->addAction(MakeFinishAction(menu->menu(), [=] {
|
||||
const auto finish = [=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
showBox(Box(
|
||||
LeaveBox,
|
||||
@@ -674,7 +673,28 @@ void FillMenu(
|
||||
true,
|
||||
BoxContext::GroupCallPanel));
|
||||
}
|
||||
}));
|
||||
};
|
||||
menu->addAction(MakeAttentionAction(
|
||||
menu->menu(),
|
||||
(real->scheduleDate()
|
||||
? tr::lng_group_call_cancel(tr::now)
|
||||
: tr::lng_group_call_end(tr::now)),
|
||||
finish));
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
const QString &text,
|
||||
Fn<void()> callback) {
|
||||
return base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
st::groupCallFinishMenu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu,
|
||||
text,
|
||||
std::move(callback)),
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace style {
|
||||
struct FlatLabel;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class DropdownMenu;
|
||||
@@ -15,6 +21,11 @@ class GenericBox;
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
class ItemBase;
|
||||
class Menu;
|
||||
} // namespace Ui::Menu
|
||||
|
||||
namespace Calls {
|
||||
class GroupCall;
|
||||
} // namespace Calls
|
||||
@@ -32,11 +43,19 @@ void LeaveBox(
|
||||
bool discardChecked,
|
||||
BoxContext context);
|
||||
|
||||
void ConfirmBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const TextWithEntities &text,
|
||||
rpl::producer<QString> button,
|
||||
Fn<void()> callback);
|
||||
struct ConfirmBoxArgs {
|
||||
TextWithEntities text;
|
||||
rpl::producer<QString> button;
|
||||
Fn<void()> callback;
|
||||
const style::FlatLabel *st = nullptr;
|
||||
Fn<bool(const ClickHandlerPtr&, Qt::MouseButton)> filter;
|
||||
};
|
||||
|
||||
void ConfirmBoxBuilder(not_null<Ui::GenericBox*> box, ConfirmBoxArgs &&args);
|
||||
|
||||
inline auto ConfirmBox(ConfirmBoxArgs &&args) {
|
||||
return Box(ConfirmBoxBuilder, std::move(args));
|
||||
}
|
||||
|
||||
void FillMenu(
|
||||
not_null<Ui::DropdownMenu*> menu,
|
||||
@@ -45,4 +64,9 @@ void FillMenu(
|
||||
Fn<void()> chooseJoinAs,
|
||||
Fn<void(object_ptr<Ui::BoxContent>)> showBox);
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
|
||||
not_null<Ui::Menu::Menu*> menu,
|
||||
const QString &text,
|
||||
Fn<void()> callback);
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
||||
@@ -38,6 +38,7 @@ class Window;
|
||||
class ScrollArea;
|
||||
class GenericBox;
|
||||
class LayerManager;
|
||||
class GroupCallScheduledLeft;
|
||||
namespace Platform {
|
||||
class TitleControls;
|
||||
} // namespace Platform
|
||||
@@ -73,34 +74,41 @@ private:
|
||||
void initWindow();
|
||||
void initWidget();
|
||||
void initControls();
|
||||
void initWithCall(GroupCall *call);
|
||||
void initShareAction();
|
||||
void initLayout();
|
||||
void initGeometry();
|
||||
void setupScheduledLabels(rpl::producer<TimeId> date);
|
||||
void setupMembers();
|
||||
void setupJoinAsChangedToasts();
|
||||
void setupTitleChangedToasts();
|
||||
void setupAllowedToSpeakToasts();
|
||||
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
|
||||
|
||||
bool handleClose();
|
||||
void startScheduledNow();
|
||||
|
||||
void updateControlsGeometry();
|
||||
void updateMembersGeometry();
|
||||
void showControls();
|
||||
void refreshLeftButton();
|
||||
|
||||
void endCall();
|
||||
|
||||
void showMainMenu();
|
||||
void chooseJoinAs();
|
||||
void addMembers();
|
||||
void kickMember(not_null<UserData*> user);
|
||||
void kickMemberSure(not_null<UserData*> user);
|
||||
void kickParticipant(not_null<PeerData*> participantPeer);
|
||||
void kickParticipantSure(not_null<PeerData*> participantPeer);
|
||||
[[nodiscard]] QRect computeTitleRect() const;
|
||||
void refreshTitle();
|
||||
void refreshTitleGeometry();
|
||||
void setupRealCallViewers(not_null<GroupCall*> call);
|
||||
void setupRealCallViewers();
|
||||
void subscribeToChanges(not_null<Data::GroupCall*> real);
|
||||
|
||||
void migrate(not_null<ChannelData*> channel);
|
||||
void subscribeToPeerChanges();
|
||||
|
||||
GroupCall *_call = nullptr;
|
||||
const not_null<GroupCall*> _call;
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
const std::unique_ptr<Ui::Window> _window;
|
||||
@@ -118,13 +126,18 @@ private:
|
||||
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
||||
object_ptr<Members> _members;
|
||||
rpl::variable<QString> _titleText;
|
||||
object_ptr<Members> _members = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _startsIn = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _countdown = { nullptr };
|
||||
std::shared_ptr<Ui::GroupCallScheduledLeft> _countdownData;
|
||||
object_ptr<Ui::FlatLabel> _startsWhen = { nullptr };
|
||||
ChooseJoinAsProcess _joinAsProcess;
|
||||
|
||||
object_ptr<Ui::CallButton> _settings;
|
||||
object_ptr<Ui::CallButton> _settings = { nullptr };
|
||||
object_ptr<Ui::CallButton> _share = { nullptr };
|
||||
std::unique_ptr<Ui::CallMuteButton> _mute;
|
||||
object_ptr<Ui::CallButton> _hangup;
|
||||
Fn<void()> _shareLinkCallback;
|
||||
|
||||
rpl::lifetime _peerLifetime;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "history/history_message.h" // GetErrorTextForSending.
|
||||
@@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "core/application.h"
|
||||
#include "boxes/single_choice_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "webrtc/webrtc_audio_input_tester.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "settings/settings_common.h"
|
||||
@@ -149,8 +149,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
||||
}
|
||||
text.append(error.first);
|
||||
if (const auto weak = *box) {
|
||||
weak->getDelegate()->show(
|
||||
Box(ConfirmBox, text, nullptr, nullptr));
|
||||
weak->getDelegate()->show(ConfirmBox({ .text = text }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -535,9 +534,10 @@ void SettingsBox(
|
||||
box->getDelegate()->show(std::move(next));
|
||||
});
|
||||
const auto showToast = crl::guard(box, [=](QString text) {
|
||||
Ui::Toast::Show(
|
||||
box->getDelegate()->outerContainer(),
|
||||
text);
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = box->getDelegate()->outerContainer(),
|
||||
.text = { text },
|
||||
});
|
||||
});
|
||||
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
|
||||
peer,
|
||||
@@ -572,9 +572,10 @@ void SettingsBox(
|
||||
}
|
||||
QGuiApplication::clipboard()->setText(link);
|
||||
if (weakBox) {
|
||||
Ui::Toast::Show(
|
||||
box->getDelegate()->outerContainer(),
|
||||
tr::lng_create_channel_link_copied(tr::now));
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = box->getDelegate()->outerContainer(),
|
||||
.text = { tr::lng_create_channel_link_copied(tr::now) },
|
||||
});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@@ -675,7 +676,7 @@ std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
|
||||
return true;
|
||||
};
|
||||
auto callback = [=] {
|
||||
const auto real = peer->groupCall();
|
||||
const auto real = peer->migrateToOrMe()->groupCall();
|
||||
if (shareReady() || state->generatingLink || !real) {
|
||||
return;
|
||||
}
|
||||
@@ -700,11 +701,11 @@ std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
|
||||
state->linkSpeakerRequestId = peer->session().api().request(
|
||||
MTPphone_ExportGroupCallInvite(
|
||||
MTP_flags(Flag::f_can_self_unmute),
|
||||
real->input()
|
||||
)).done([=](const MTPphone_ExportedGroupCallInvite &result) {
|
||||
real->input())
|
||||
).done([=](const MTPphone_ExportedGroupCallInvite &result) {
|
||||
state->linkSpeakerRequestId = 0;
|
||||
result.match([&](
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
const MTPDphone_exportedGroupCallInvite &data) {
|
||||
state->linkSpeaker = qs(data.vlink());
|
||||
shareReady();
|
||||
});
|
||||
|
||||
@@ -374,7 +374,7 @@ void Instance::handleCallUpdate(
|
||||
const MTPPhoneCall &call) {
|
||||
if (call.type() == mtpc_phoneCallRequested) {
|
||||
auto &phoneCall = call.c_phoneCallRequested();
|
||||
auto user = session->data().userLoaded(phoneCall.vadmin_id().v);
|
||||
auto user = session->data().userLoaded(phoneCall.vadmin_id());
|
||||
if (!user) {
|
||||
LOG(("API Error: User not loaded for phoneCallRequested."));
|
||||
} else if (user->isSelf()) {
|
||||
@@ -411,6 +411,14 @@ void Instance::handleCallUpdate(
|
||||
void Instance::handleGroupCallUpdate(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update) {
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
|
||||
return data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
@@ -427,14 +435,6 @@ void Instance::handleGroupCallUpdate(
|
||||
} else {
|
||||
applyGroupCallUpdateChecked(session, update);
|
||||
}
|
||||
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::applyGroupCallUpdateChecked(
|
||||
|
||||
@@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "core/application.h"
|
||||
#include "mainwindow.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -185,7 +184,7 @@ void Panel::Incoming::fillBottomShadow(QPainter &p) {
|
||||
Panel::Panel(not_null<Call*> call)
|
||||
: _call(call)
|
||||
, _user(call->user())
|
||||
, _window(std::make_unique<Ui::Window>(Core::App().getModalParent()))
|
||||
, _window(std::make_unique<Ui::Window>())
|
||||
#ifndef Q_OS_MAC
|
||||
, _controls(std::make_unique<Ui::Platform::TitleControls>(
|
||||
_window->body(),
|
||||
|
||||
@@ -57,8 +57,13 @@ constexpr auto kHideBlobsDuration = crl::time(500);
|
||||
constexpr auto kBlobLevelDuration = crl::time(250);
|
||||
constexpr auto kBlobUpdateInterval = crl::time(100);
|
||||
|
||||
auto BarStateFromMuteState(MuteState state, GroupCall::InstanceState instanceState) {
|
||||
return (instanceState == GroupCall::InstanceState::Disconnected)
|
||||
auto BarStateFromMuteState(
|
||||
MuteState state,
|
||||
GroupCall::InstanceState instanceState,
|
||||
TimeId scheduledDate) {
|
||||
return scheduledDate
|
||||
? BarState::ForceMuted
|
||||
: (instanceState == GroupCall::InstanceState::Disconnected)
|
||||
? BarState::Connecting
|
||||
: (state == MuteState::ForceMuted || state == MuteState::RaisedHand)
|
||||
? BarState::ForceMuted
|
||||
@@ -293,19 +298,27 @@ void TopBar::initControls() {
|
||||
_call
|
||||
? mapToState(_call->muted())
|
||||
: _groupCall->muted(),
|
||||
GroupCall::InstanceState::Connected));
|
||||
GroupCall::InstanceState::Connected,
|
||||
_call ? TimeId(0) : _groupCall->scheduleDate()));
|
||||
using namespace rpl::mappers;
|
||||
auto muted = _call
|
||||
? rpl::combine(
|
||||
_call->mutedValue() | rpl::map(mapToState),
|
||||
rpl::single(GroupCall::InstanceState::Connected)
|
||||
rpl::single(GroupCall::InstanceState::Connected),
|
||||
rpl::single(TimeId(0))
|
||||
) | rpl::type_erased()
|
||||
: rpl::combine(
|
||||
(_groupCall->mutedValue()
|
||||
| MapPushToTalkToActive()
|
||||
| rpl::distinct_until_changed()
|
||||
| rpl::type_erased()),
|
||||
_groupCall->instanceStateValue()
|
||||
_groupCall->instanceStateValue(),
|
||||
rpl::single(
|
||||
_groupCall->scheduleDate()
|
||||
) | rpl::then(_groupCall->real(
|
||||
) | rpl::map([](not_null<Data::GroupCall*> call) {
|
||||
return call->scheduleDateValue();
|
||||
}) | rpl::flatten_latest())
|
||||
) | rpl::filter(_2 != GroupCall::InstanceState::TransitionToRtc);
|
||||
std::move(
|
||||
muted
|
||||
@@ -625,8 +638,8 @@ void TopBar::setInfoLabels() {
|
||||
const auto user = call->user();
|
||||
const auto fullName = user->name;
|
||||
const auto shortName = user->firstName;
|
||||
_fullInfoLabel->setText(fullName.toUpper());
|
||||
_shortInfoLabel->setText(shortName.toUpper());
|
||||
_fullInfoLabel->setText(fullName);
|
||||
_shortInfoLabel->setText(shortName);
|
||||
} else if (const auto group = _groupCall.get()) {
|
||||
const auto peer = group->peer();
|
||||
const auto real = peer->groupCall();
|
||||
@@ -634,8 +647,8 @@ void TopBar::setInfoLabels() {
|
||||
const auto text = _isGroupConnecting.current()
|
||||
? tr::lng_group_call_connecting(tr::now)
|
||||
: (real && real->id() == group->id() && !real->title().isEmpty())
|
||||
? real->title().toUpper()
|
||||
: name.toUpper();
|
||||
? real->title()
|
||||
: name;
|
||||
_fullInfoLabel->setText(text);
|
||||
_shortInfoLabel->setText(text);
|
||||
}
|
||||
|
||||
@@ -163,14 +163,20 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
}
|
||||
} else if (const auto user = item->history()->peer->asUser()) {
|
||||
if (user->isBot() && item->from() == user) {
|
||||
if (key == Qt::Key_Q) {
|
||||
if (key == Qt::Key_Q || key == Qt::Key_6) {
|
||||
App::sendBotCommand(user, user, qsl("/translate"));
|
||||
} else if (key == Qt::Key_W) {
|
||||
} else if (key == Qt::Key_W || key == Qt::Key_5) {
|
||||
App::sendBotCommand(user, user, qsl("/eng"));
|
||||
} else if (key == Qt::Key_3) {
|
||||
App::sendBotCommand(user, user, qsl("/pattern"));
|
||||
} else if (key == Qt::Key_4) {
|
||||
App::sendBotCommand(user, user, qsl("/abuse"));
|
||||
} else if (key == Qt::Key_0 || key == Qt::Key_E) {
|
||||
App::sendBotCommand(user, user, qsl("/undo"));
|
||||
} else if (key == Qt::Key_Plus || key == Qt::Key_QuoteLeft) {
|
||||
App::sendBotCommand(user, user, qsl("/next"));
|
||||
} else if (key == Qt::Key_Period || key == Qt::Key_S) {
|
||||
App::sendBotCommand(user, user, qsl("/stats"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ EmojiListWidget::EmojiListWidget(
|
||||
for (auto i = 0; i != kEmojiSectionCount; ++i) {
|
||||
const auto section = static_cast<Section>(i);
|
||||
_counts[i] = (section == Section::Recent)
|
||||
? GetRecentEmoji().size()
|
||||
? int(Core::App().settings().recentEmoji().size())
|
||||
: Ui::Emoji::GetSectionCount(section);
|
||||
}
|
||||
|
||||
@@ -500,17 +500,18 @@ void EmojiListWidget::ensureLoaded(int section) {
|
||||
return;
|
||||
}
|
||||
_emoji[section] = (static_cast<Section>(section) == Section::Recent)
|
||||
? GetRecentEmojiSection()
|
||||
? Core::App().settings().recentEmojiSection()
|
||||
: Ui::Emoji::GetSection(static_cast<Section>(section));
|
||||
_counts[section] = _emoji[section].size();
|
||||
if (static_cast<Section>(section) == Section::Recent) {
|
||||
return;
|
||||
}
|
||||
const auto &variants = Core::App().settings().emojiVariants();
|
||||
for (auto &emoji : _emoji[section]) {
|
||||
if (emoji->hasVariants()) {
|
||||
auto j = cEmojiVariants().constFind(emoji->nonColoredId());
|
||||
if (j != cEmojiVariants().cend()) {
|
||||
emoji = emoji->variant(j.value());
|
||||
const auto j = variants.find(emoji->nonColoredId());
|
||||
if (j != end(variants)) {
|
||||
emoji = emoji->variant(j->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,10 +595,13 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) {
|
||||
if (_selected >= 0) {
|
||||
auto section = (_selected / MatrixRowShift);
|
||||
auto sel = _selected % MatrixRowShift;
|
||||
if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) {
|
||||
if (section < kEmojiSectionCount
|
||||
&& sel < _emoji[section].size()
|
||||
&& _emoji[section][sel]->hasVariants()) {
|
||||
_pickerSel = _selected;
|
||||
setCursor(style::cur_default);
|
||||
if (!cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) {
|
||||
const auto &variants = Core::App().settings().emojiVariants();
|
||||
if (!variants.contains(_emoji[section][sel]->nonColoredId())) {
|
||||
showPicker();
|
||||
} else {
|
||||
_showPickerTimer.callOnce(500);
|
||||
@@ -617,8 +621,11 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
} else if (_pickerSel >= 0) {
|
||||
auto section = (_pickerSel / MatrixRowShift);
|
||||
auto sel = _pickerSel % MatrixRowShift;
|
||||
if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) {
|
||||
if (cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) {
|
||||
if (section < kEmojiSectionCount
|
||||
&& sel < _emoji[section].size()
|
||||
&& _emoji[section][sel]->hasVariants()) {
|
||||
const auto &variants = Core::App().settings().emojiVariants();
|
||||
if (variants.contains(_emoji[section][sel]->nonColoredId())) {
|
||||
_picker->hideAnimated();
|
||||
_pickerSel = -1;
|
||||
}
|
||||
@@ -650,7 +657,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void EmojiListWidget::selectEmoji(EmojiPtr emoji) {
|
||||
AddRecentEmoji(emoji);
|
||||
Core::App().settings().incrementRecentEmoji(emoji);
|
||||
_chosen.fire_copy(emoji);
|
||||
}
|
||||
|
||||
@@ -698,10 +705,7 @@ QRect EmojiListWidget::emojiRect(int section, int sel) {
|
||||
|
||||
void EmojiListWidget::colorChosen(EmojiPtr emoji) {
|
||||
if (emoji->hasVariants()) {
|
||||
cRefEmojiVariants().insert(
|
||||
emoji->nonColoredId(),
|
||||
emoji->variantIndex(emoji));
|
||||
controller()->session().saveSettingsDelayed();
|
||||
Core::App().settings().saveEmojiVariant(emoji);
|
||||
}
|
||||
if (_pickerSel >= 0) {
|
||||
auto section = (_pickerSel / MatrixRowShift);
|
||||
@@ -790,7 +794,7 @@ void EmojiListWidget::processHideFinished() {
|
||||
|
||||
void EmojiListWidget::refreshRecent() {
|
||||
clearSelection();
|
||||
_emoji[0] = GetRecentEmojiSection();
|
||||
_emoji[0] = Core::App().settings().recentEmojiSection();
|
||||
_counts[0] = _emoji[0].size();
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
@@ -116,11 +116,11 @@ auto SuggestionsWidget::getRowsByQuery() const -> std::vector<Row> {
|
||||
}) | ranges::to_vector;
|
||||
|
||||
auto lastRecent = begin(result);
|
||||
const auto &recent = GetRecentEmoji();
|
||||
const auto &recent = Core::App().settings().recentEmoji();
|
||||
for (const auto &item : recent) {
|
||||
const auto emoji = item.first->original()
|
||||
? item.first->original()
|
||||
: item.first;
|
||||
const auto emoji = item.emoji->original()
|
||||
? item.emoji->original()
|
||||
: item.emoji;
|
||||
const auto it = ranges::find(result, emoji, [](const Row &row) {
|
||||
return row.emoji.get();
|
||||
});
|
||||
@@ -133,12 +133,12 @@ auto SuggestionsWidget::getRowsByQuery() const -> std::vector<Row> {
|
||||
for (auto &item : result) {
|
||||
item.emoji = [&] {
|
||||
const auto result = item.emoji;
|
||||
const auto &variants = cEmojiVariants();
|
||||
const auto &variants = Core::App().settings().emojiVariants();
|
||||
const auto i = result->hasVariants()
|
||||
? variants.constFind(result->nonColoredId())
|
||||
: variants.cend();
|
||||
return (i != variants.cend())
|
||||
? result->variant(i.value())
|
||||
? variants.find(result->nonColoredId())
|
||||
: end(variants);
|
||||
return (i != end(variants))
|
||||
? result->variant(i->second)
|
||||
: result.get();
|
||||
}();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ QString FieldTagMimeProcessor::tagFromMimeTag(const QString &mimeTag) {
|
||||
const auto userId = _controller->session().userId();
|
||||
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
|
||||
if (!match.hasMatch()
|
||||
|| match.capturedRef(1).toInt() != userId) {
|
||||
|| match.capturedRef(1).toULongLong() != userId.bare) {
|
||||
return QString();
|
||||
}
|
||||
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
|
||||
@@ -249,7 +249,7 @@ TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
|
||||
|
||||
QString PrepareMentionTag(not_null<UserData*> user) {
|
||||
return TextUtilities::kMentionTagStart
|
||||
+ QString::number(user->bareId())
|
||||
+ QString::number(user->id.value)
|
||||
+ '.'
|
||||
+ QString::number(user->accessHash());
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
enum {
|
||||
MaxSelectedItems = 100,
|
||||
|
||||
MaxPhoneCodeLength = 4, // max length of country phone code
|
||||
MaxPhoneTailLength = 32, // rest of the phone number, without country code (seen 12 at least), need more for service numbers
|
||||
|
||||
LocalEncryptIterCount = 4000, // key derivation iteration count
|
||||
LocalEncryptNoPwdIterCount = 4, // key derivation iteration count without pwd (not secure anyway)
|
||||
LocalEncryptSaltSize = 32, // 256 bit
|
||||
@@ -75,7 +72,7 @@ w/CVnbwQOw0g5GBwwFV3r0uTTvy44xx8XXxk+Qknu4eBCsmrAFNnAgMBAAE=\n\
|
||||
#if defined TDESKTOP_API_ID && defined TDESKTOP_API_HASH
|
||||
|
||||
constexpr auto ApiId = TDESKTOP_API_ID;
|
||||
constexpr auto ApiHash = MACRO_TO_STRING(TDESKTOP_API_HASH);
|
||||
constexpr auto ApiHash = QT_STRINGIFY(TDESKTOP_API_HASH);
|
||||
|
||||
#else // TDESKTOP_API_ID && TDESKTOP_API_HASH
|
||||
|
||||
|
||||