Compare commits

..

88 Commits

Author SHA1 Message Date
John Preston
672aacd528 wip linux webview 2021-03-31 22:20:30 +04:00
John Preston
1a5e807fa9 Fix build for macOS / Linux. 2021-03-31 21:38:32 +04:00
John Preston
f98f4f0d14 Simple receipt viewing. 2021-03-30 10:03:54 +04:00
John Preston
78def16ced Fix showing comments from the beginning. 2021-03-29 19:53:55 +04:00
John Preston
cd7b3419de Add phone format and validation in payments. 2021-03-29 18:56:26 +04:00
John Preston
dc4048f1c1 Add local validation for card information. 2021-03-29 16:16:54 +04:00
John Preston
bef5320163 Add nice country choosing in payments. 2021-03-26 21:09:09 +04:00
John Preston
9a722ea8d4 Improve checkout information / card page design. 2021-03-26 19:23:12 +04:00
John Preston
1aefada45d Improve checkout main page design. 2021-03-26 17:05:31 +04:00
John Preston
f5a8bf0e74 Handle native / non-native payment methods (same way). 2021-03-25 23:22:45 +04:00
John Preston
738439c334 Support entering card details natively. 2021-03-25 20:56:20 +04:00
John Preston
8949c9969b Fix jumping of Media Viewer in some DEs. 2021-03-25 19:10:01 +04:00
John Preston
cdf6fb1512 Port required parts of Stripe SDK to lib_stripe. 2021-03-24 22:00:49 +04:00
John Preston
fb0ea59ff3 Validate saved information on payment form open. 2021-03-24 16:41:46 +04:00
John Preston
36f5be60f4 Show some payment errors, focus fields. 2021-03-24 15:30:01 +04:00
John Preston
46508f7e5e First full-featured version of payments, no design. 2021-03-23 22:30:00 +04:00
John Preston
28137dfb60 Start proper payments processing. 2021-03-23 16:34:34 +04:00
John Preston
e7784620d3 Use navigation cancel in Webview. 2021-03-22 22:56:29 +04:00
John Preston
462986e9c3 Update lib_webview. 2021-03-22 22:56:29 +04:00
John Preston
c11de2380e Start Linux support. 2021-03-22 22:56:27 +04:00
John Preston
a432e826a6 Use lib_webview implementation on macOS. 2021-03-22 22:54:54 +04:00
John Preston
ea9e85e70f Use lib_webview implementation on Windows. 2021-03-22 22:54:54 +04:00
John Preston
d3829c52ec Fix webview on macOS. 2021-03-22 22:54:54 +04:00
John Preston
9f04570335 3DSecure in Proof-Of-Concept payments. 2021-03-22 22:54:53 +04:00
John Preston
3c486522a7 Proof-Of-Concept simplest invoice payment. 2021-03-22 22:54:53 +04:00
John Preston
07cd8c4e83 Link Telegram with lib_webview. 2021-03-22 22:54:53 +04:00
John Preston
7447c6ea75 Add webview / lib_webview submodules. 2021-03-22 22:54:53 +04:00
John Preston
4d579f873c Remove tested 110% scale. 2021-03-22 19:52:51 +04:00
John Preston
56c8327746 Fix emoji picker button in boxes in non-default scale. 2021-03-22 19:41:13 +04:00
John Preston
0e6d4291a2 Fix close window button paint in theme preview. 2021-03-22 19:40:12 +04:00
John Preston
8ca622d077 Remove 100% from voice chat 'speaking' status. 2021-03-22 18:55:07 +04:00
John Preston
4d24f28fd0 By voicechat link open the channel as well. 2021-03-22 17:24:53 +04:00
John Preston
2b3469ef22 Remove CAPS in calls / voice chats top bar. 2021-03-22 16:44:00 +04:00
John Preston
03a868a6e3 Allow skipping stuck files in data export.
Fixes #6423.
2021-03-22 16:32:40 +04:00
Ilya Fedin
12db51fe75 Avoid removing portal platformtheme plugin in snap 2021-03-22 07:57:18 +03:00
Ilya Fedin
ce5579e8f9 Unify gtk/xdp file dialog getters 2021-03-22 07:57:18 +03:00
Ilya Fedin
a16b7fbb83 Fix path to executable in ComputeExternalUpdater 2021-03-21 09:04:43 +03:00
Ilya Fedin
9f6f7f7c9b Fix build without dbus 2021-03-21 08:59:19 +03:00
John Preston
a82d1e863e Version 2.7.1: Fix channels ban in admin log. 2021-03-20 18:19:31 +04:00
John Preston
26d97a3636 Version 2.7.1.
- Fix editing 'Manage Voice Chats' rights for channel admins.
- Fix verification check display in voice chat participants list.
- Allow removing and blocking channels from voice chats.
2021-03-20 16:23:41 +04:00
John Preston
7b8e421996 Allow markup in some voice chat toasts. 2021-03-20 16:23:41 +04:00
John Preston
2bc2a0e459 Fix possible integer overflow. 2021-03-20 16:03:58 +04:00
John Preston
7cb4b4f8ab Don't try to use WebRTC built-in audio backends. 2021-03-20 15:43:35 +04:00
John Preston
b439ecce16 Allow all toasts to be multiline in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a33a4c0589 Fix maximize/restore voice chat title bar button. 2021-03-20 15:43:35 +04:00
John Preston
5278e2201f Make red 'Remove' in voice chat participant menu. 2021-03-20 15:43:35 +04:00
John Preston
3bd6b2268f Allow blocking channels in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a0a13c3b86 Update API scheme to layer 126. 2021-03-20 15:43:35 +04:00
John Preston
0052c7938f Fix verified icon in voice chat participants list. 2021-03-20 15:43:35 +04:00
John Preston
a14db3e492 Allow editing 'Manage Voice Chats' admin right in channels. 2021-03-20 15:43:35 +04:00
Ilya Fedin
7979b3b6c8 Fix devtoolset version in linux action 2021-03-20 14:33:02 +03:00
Ilya Fedin
3f25e92afd Add build options for libtgvoip backends
libtgvoip has options to disbale some backends, but they never were exposed via tdesktop's cmake

Since libtgvoip autoconf build system doesn't work anymore, it's worth to have these options in tdesktop's cmake.
2021-03-20 14:33:02 +03:00
Ilya Fedin
3d1cddaca5 Add a way to change default handler in snap 2021-03-20 14:20:09 +03:00
John Preston
eeecc42c25 Version 2.7.
- Start limitless Voice Chats in Groups and Channels.
- Host discussions that can be listened to
by millions of people simultaneously.
- Record voice chats to share or publish in Channels later.
- See that a chat is being recorded
from the red dot next to its title.
- See user bio texts right from the list of participants.
- Raise your hand to show admins you want to speak.
- Create separate Voice Chat Invite Links for listeners or speakers.
- Change the title of your Voice Chat
to give people an idea of the current topic.
- Join Voice Chats as one of your Channels
to hide your personal account.
2021-03-19 14:15:26 +04:00
John Preston
e22ecafc1d Add confirmation on create / anonymous admin join. 2021-03-19 14:10:02 +04:00
John Preston
ba41da7b28 Fix discarded group call handle. 2021-03-19 00:57:16 +04:00
John Preston
9cfbccf9e7 Beta version 2.6.8.
- Fix connecting and getting allowed to speak on voice chats.
- MPRIS support on Linux.
2021-03-18 22:56:42 +04:00
John Preston
2b6f50e114 Fix joining / unmuting. 2021-03-18 22:56:42 +04:00
Ilya Fedin
d2f57b72c3 Add mpris permission for snap 2021-03-18 21:55:06 +03:00
Ilya Fedin
85ac983a27 Add MPRIS support 2021-03-18 21:55:06 +03:00
John Preston
ac397e6e19 Beta version 2.6.7.
- Improve voice chat participants list updating.
2021-03-18 18:05:43 +04:00
John Preston
38e15c9bdb Fix saving legacy chat admins without migration.
Fixes #10558.
2021-03-18 17:27:33 +04:00
John Preston
00d65fa978 Request one participants slice on voice chat reload. 2021-03-18 16:58:05 +04:00
John Preston
3fea9cca08 Subscribe to channel updates in voice chat. 2021-03-18 15:54:28 +04:00
John Preston
b390e0766b Apply all queued updates on reload. 2021-03-18 15:30:58 +04:00
John Preston
2f75e6bbe2 Add some logging for voice chat updates. 2021-03-18 15:22:55 +04:00
Ilya Fedin
decbbb9a73 Check for openal fork updates in Dockerfile 2021-03-18 07:51:57 +03:00
Ilya Fedin
b4b80822c8 Set glib's application name and prgname 2021-03-18 07:51:57 +03:00
John Preston
bc82cdc3b3 Call dump_syms and strip outside of docker. 2021-03-18 02:33:29 +04:00
John Preston
ebc67d25f0 Migrate docker build to GCC 9. 2021-03-18 00:30:12 +04:00
John Preston
348b4d54ba Revert "Workaround build issues on GCC."
This reverts commit 3defb06783.
2021-03-18 00:30:12 +04:00
John Preston
6f86ce595b Beta version 2.6.6.
- Fix joining popular voice chats.
2021-03-18 00:26:53 +04:00
John Preston
8c53a3c19e Don't skip updateGroupCallParticipants while in getDifference. 2021-03-18 00:25:53 +04:00
John Preston
67623072d6 Fix joining a voice chat. 2021-03-18 00:24:36 +04:00
John Preston
1291f1c80d Beta version 2.6.5.
- Improvements and fixes in new voice chat features.
2021-03-17 21:16:55 +04:00
John Preston
0684db9bd8 Improve participants sorting in voice chats. 2021-03-17 20:37:55 +04:00
John Preston
db7b61a77b Rewrite voice chat members list management. 2021-03-17 20:37:55 +04:00
John Preston
d392633b90 Send speaking typings in channels. 2021-03-17 20:37:55 +04:00
John Preston
76e08af26a Apply updateGroupCallParticipants before updateGroupCall. 2021-03-17 20:37:55 +04:00
John Preston
b23f16e6e4 Don't show 'allowed to speak' on first join. 2021-03-17 20:37:54 +04:00
23rd
23156d523c Fixed Github CI Windows build. 2021-03-17 18:59:20 +03:00
Ilya Fedin
04b0e2e9e6 Update submodules 2021-03-17 18:58:02 +03:00
Ilya Fedin
ace5740125 Use QProcess::startDetached for xdg-open
Since it may running continously
2021-03-17 15:39:26 +03:00
John Preston
bc67b79023 Beta version 2.6.4: 110% UI scale on macOS Retina. 2021-03-17 00:07:08 +04:00
John Preston
528c98af67 Beta version 2.6.4.
- Fix freeze in voice chats.
2021-03-17 00:02:05 +04:00
John Preston
311a2f2753 Fix freeze in voice chats. 2021-03-16 23:54:58 +04:00
John Preston
3defb06783 Workaround build issues on GCC. 2021-03-16 22:26:58 +04:00
John Preston
5708b5e849 Fix confirmation when joining by link. 2021-03-16 21:06:36 +04:00
191 changed files with 8992 additions and 1903 deletions

View File

@@ -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"

View File

@@ -128,7 +128,7 @@ jobs:
shell: bash
run: |
echo "Find any version of Python 2."
p=`ls /c/hostedtoolcache/windows/python | grep 2 | tail -1`
p=`ls /c/hostedtoolcache/windows/python | grep "^2" | tail -1`
if [ -z "$p" ]; then
echo "Python 2 is not found."
exit 1
@@ -409,6 +409,7 @@ jobs:
-D TDESKTOP_API_TEST=ON ^
-D DESKTOP_APP_USE_PACKAGED=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^
%TDESKTOP_BUILD_DEFINE% ^
-DCMAKE_SYSTEM_VERSION=%SDK%

3
.gitmodules vendored
View File

@@ -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

View File

@@ -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)
@@ -55,7 +57,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 +74,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 +258,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 +390,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
@@ -796,8 +801,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 +811,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 +828,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 +1014,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 +1029,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 +1118,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

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1860,6 +1860,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_invoice_label_test" = "Test invoice";
"lng_payments_receipt_button" = "Receipt";
"lng_payments_checkout_title" = "Checkout";
"lng_payments_receipt_title" = "Receipt";
"lng_payments_total_label" = "Total";
"lng_payments_date_label" = "Date";
"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_shipping_address_title" = "Shipping Information";
"lng_payments_save_shipping_about" = "You can save your shipping information for future use.";
"lng_payments_card_title" = "New Card";
"lng_payments_card_number" = "Card Number";
"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_payment_about" = "You can save your payment information for future use.";
"lng_payments_save_information" = "Save Information";
"lng_call_status_incoming" = "is calling you...";
"lng_call_status_connecting" = "connecting...";
"lng_call_status_exchanging" = "exchanging encryption keys...";
@@ -1939,6 +1969,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_leave_sure" = "Are you sure you want to leave this 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";
@@ -1980,6 +2012,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";
@@ -2222,6 +2256,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 +2504,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.";

View File

@@ -620,8 +620,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 +632,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;
@@ -1203,7 +1203,7 @@ 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 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#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;
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
@@ -1557,7 +1557,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;
@@ -1572,8 +1572,8 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> =
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 for_groupcall:flags.2?true = messages.Chats;
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
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;
@@ -1645,4 +1645,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 126

View File

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

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,3,0
PRODUCTVERSION 2,6,3,0
FILEVERSION 2,7,1,0
PRODUCTVERSION 2,7,1,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.3.0"
VALUE "FileVersion", "2.7.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.3.0"
VALUE "ProductVersion", "2.7.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,6,3,0
PRODUCTVERSION 2,6,3,0
FILEVERSION 2,7,1,0
PRODUCTVERSION 2,7,1,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.3.0"
VALUE "FileVersion", "2.7.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.6.3.0"
VALUE "ProductVersion", "2.7.1.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -65,7 +65,10 @@ void SendProgressManager::update(
SendProgressType type,
int progress) {
const auto peer = history->peer;
if (peer->isSelf() || (peer->isChannel() && !peer->isMegagroup())) {
if (peer->isSelf()
|| (peer->isChannel()
&& !peer->isMegagroup()
&& type != SendProgressType::Speaking)) {
return;
}

View File

@@ -277,12 +277,32 @@ void Updates::checkLastUpdate(bool afterSleep) {
void Updates::feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds) {
for (const auto &update : updates.v) {
if (skipMessageIds && update.type() == mtpc_updateMessageID) {
SkipUpdatePolicy policy) {
auto list = updates.v;
const auto hasGroupCallParticipantUpdates = ranges::contains(
list,
mtpc_updateGroupCallParticipants,
&MTPUpdate::type);
if (hasGroupCallParticipantUpdates) {
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
if (entry.type() == mtpc_updateGroupCallParticipants) {
return 0;
} else {
return 1;
}
});
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
return;
}
for (const auto &entry : std::as_const(list)) {
const auto type = entry.type();
if ((policy == SkipUpdatePolicy::SkipMessageIds
&& type == mtpc_updateMessageID)
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
&& type != mtpc_updateGroupCallParticipants)) {
continue;
}
feedUpdate(update);
feedUpdate(entry);
}
session().data().sendHistoryChangeNotifications();
}
@@ -392,7 +412,9 @@ void Updates::feedChannelDifference(
session().data().processMessages(
data.vnew_messages(),
NewMessageType::Unread);
feedUpdateVector(data.vother_updates(), true);
feedUpdateVector(
data.vother_updates(),
SkipUpdatePolicy::SkipMessageIds);
_handlingChannelDifference = false;
}
@@ -553,7 +575,7 @@ void Updates::feedDifference(
session().data().processChats(chats);
feedMessageIds(other);
session().data().processMessages(msgs, NewMessageType::Unread);
feedUpdateVector(other, true);
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
}
void Updates::differenceFail(const MTP::Error &error) {
@@ -810,9 +832,32 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
if (!requestingDifference()
|| HasForceLogoutNotification(updates)) {
applyUpdates(updates);
} else {
applyGroupCallParticipantUpdates(updates);
}
}
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
updates.match([&](const MTPDupdates &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdatesCombined &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdateShort &data) {
if (data.vupdate().type() == mtpc_updateGroupCallParticipants) {
feedUpdate(data.vupdate());
}
}, [](const auto &) {
});
}
int32 Updates::pts() const {
return _ptsWaiter.current();
}

View File

@@ -66,6 +66,12 @@ private:
AfterFail,
};
enum class SkipUpdatePolicy {
SkipNone,
SkipMessageIds,
SkipExceptGroupCallParticipants,
};
struct ActiveChatTracker {
PeerData *peer = nullptr;
rpl::lifetime lifetime;
@@ -113,12 +119,14 @@ private:
void mtpNewSessionCreated();
void feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds = false);
SkipUpdatePolicy policy = SkipUpdatePolicy::SkipNone);
// Doesn't call sendHistoryChangeNotifications itself.
void feedMessageIds(const MTPVector<MTPUpdate> &updates);
// Doesn't call sendHistoryChangeNotifications itself.
void feedUpdate(const MTPUpdate &update);
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
bool whenGetDiffChanged(
ChannelData *channel,
int32 ms,

View File

@@ -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 &currentRights) {
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 {
@@ -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);
}
});
}
}

View File

@@ -249,14 +249,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 &currentRights);
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 +659,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;

View File

@@ -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()) {

View File

@@ -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());

View File

@@ -900,7 +900,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(

View File

@@ -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,

View File

@@ -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:

View File

@@ -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;
@@ -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,61 @@ 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(
MTP_int(user->bareId()),
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()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
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,37 +833,39 @@ 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)
|| (_excludeSelf && participant->isSelf())) {
return false;
}
delegate()->peerListAppendRow(createRow(user));
delegate()->peerListAppendRow(createRow(participant));
return true;
}
@@ -861,8 +878,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(

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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();
@@ -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 (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().v)) {
const auto i = _restrictedBy.find(user);
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(
@@ -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,31 +1425,32 @@ 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);
}
}
@@ -1433,28 +1461,30 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
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); }));
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,7 +1493,7 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
: tr::lng_context_promote_admin)(tr::now),
crl::guard(this, [=] { showAdmin(user); }));
}
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(participant)) {
const auto canRestrictWithoutKick = [&] {
if (const auto chat = _peer->asChat()) {
return chat->amCreator();
@@ -1476,14 +1506,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
crl::guard(this, [=] { showRestricted(user); }));
}
}
if (_additional.canRemoveUser(user)) {
if (!_additional.isKicked(user)) {
if (_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;
@@ -1569,9 +1599,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 +1608,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,72 +1625,84 @@ 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(
MTP_int(user->bareId()),
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()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
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)) {
delegate()->peerListRemoveRow(row);
@@ -1671,25 +1711,24 @@ void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
_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)) {
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 +1769,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)) {
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)) {
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 +1809,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)) {
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 +1828,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)) {
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 +1847,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 +1886,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)) {
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 +1929,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)(

View File

@@ -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);

View File

@@ -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"

View File

@@ -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) }
};
}

View File

@@ -163,6 +163,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() {
@@ -240,38 +271,6 @@ void ChooseJoinAsProcess::start(
if (list.empty()) {
_request->showToast(Lang::Hard::ServerError());
return;
} else if (!changingJoinAsFrom
&& list.size() == 1
&& list.front() == self
&& (!peer->isChannel()
|| !peer->asChannel()->amAnonymous()
|| (peer->isBroadcast() && !peer->canWrite()))) {
info.possibleJoinAs = std::move(list);
if (context != Context::JoinWithConfirm
|| (selectedId && self->id == selectedId)) {
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(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
tr::lng_group_call_join(tr::now),
crl::guard(&_request->guard, [=] { finish(info); }));
box->boxClosing(
) | rpl::start_with_next([=] {
_request = nullptr;
}, _request->lifetime);
_request->box = box.data();
_request->showBox(std::move(box));
return;
}
info.joinAs = [&]() -> not_null<PeerData*> {
const auto loaded = selectedId
@@ -288,11 +287,36 @@ void ChooseJoinAsProcess::start(
}();
info.possibleJoinAs = std::move(list);
if (!changingJoinAsFrom
&& selectedId
&& info.joinAs->id == selectedId) {
// We already joined this voice chat, just rejoin with the same.
finish(info);
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
&& (info.possibleJoinAs.front() == self);
// We already joined this voice chat, just rejoin with the same.
const auto byAlreadyUsed = selectedId
&& (info.joinAs->id == selectedId)
&& (peer->groupCall() != nullptr);
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
const auto confirmation = CreateOrJoinConfirmation(
peer,
context,
byAlreadyUsed);
if (confirmation.text.isEmpty()) {
finish(info);
return;
}
auto box = Box<::ConfirmBox>(
confirmation,
(peer->groupCall()
? tr::lng_group_call_join(tr::now)
: tr::lng_create_group_create(tr::now)),
crl::guard(&_request->guard, [=] { finish(info); }));
box->boxClosing(
) | rpl::start_with_next([=] {
_request = nullptr;
}, _request->lifetime);
_request->box = box.data();
_request->showBox(std::move(box));
return;
}
auto box = Box(

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_common.h"
#include "main/main_session.h"
#include "api/api_send_progress.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "lang/lang_hardcoded.h"
@@ -272,11 +273,6 @@ void GroupCall::setState(State state) {
if (state == State::Joined) {
stopConnectingSound();
if (!_hadJoinedState) {
_hadJoinedState = true;
applyGlobalShortcutChanges();
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
}
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
call->setInCall();
}
@@ -392,8 +388,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
addParticipantsToInstance();
_peer->session().updates().addActiveChat(
_peerStream.events_starting_with_copy(_peer));
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
_peer = group;
_peerStream.fire_copy(group);
});
}
@@ -472,6 +471,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
@@ -479,6 +479,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
applyMeInCallLocally();
maybeSendMutedUpdate(wasMuteState);
_peer->session().api().applyUpdates(updates);
checkFirstTimeJoined();
}).fail([=](const MTP::Error &error) {
const auto type = error.type();
LOG(("Call Error: Could not join, error: %1").arg(type));
@@ -547,7 +548,7 @@ void GroupCall::applyMeInCallLocally() {
| Flag::f_volume_by_admin // Self volume can only be set by admin.
| ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0))
| (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0));
call->applyUpdateChecked(
call->applyLocalUpdate(
MTP_updateGroupCallParticipants(
inputCall(),
MTP_vector<MTPGroupCallParticipant>(
@@ -592,7 +593,7 @@ void GroupCall::applyParticipantLocally(
| (participant->raisedHandRating
? Flag::f_raise_hand_rating
: Flag(0));
_peer->groupCall()->applyUpdateChecked(
_peer->groupCall()->applyLocalUpdate(
MTP_updateGroupCallParticipants(
inputCall(),
MTP_vector<MTPGroupCallParticipant>(
@@ -717,101 +718,112 @@ void GroupCall::setMutedAndUpdate(MuteState mute) {
}
}
void GroupCall::handleUpdate(const MTPGroupCall &call) {
return call.match([&](const MTPDgroupCall &data) {
if (_acceptFields) {
if (!_instance && !_id) {
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
}
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCall &data) {
data.vcall().match([&](const MTPDgroupCall &data) {
handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDgroupCallDiscarded &data) {
handlePossibleDiscarded(data);
});
}
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDgroupCall &data) {
if (_acceptFields) {
if (!_instance && !_id) {
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
}
return;
} else if (_id != data.vid().v || !_instance) {
return;
}
const auto streamDcId = MTP::BareDcId(
data.vstream_dc_id().value_or_empty());
const auto params = data.vparams();
if (!params) {
return;
}
params->match([&](const MTPDdataJSON &data) {
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
data.vdata().v,
&error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: "
"Failed to parse group call params, error: %1."
).arg(error.errorString()));
return;
} else if (_id != data.vid().v
|| _accessHash != data.vaccess_hash().v
|| !_instance) {
} else if (!document.isObject()) {
LOG(("API Error: "
"Not an object received in group call params."));
return;
}
const auto streamDcId = MTP::BareDcId(
data.vstream_dc_id().value_or_empty());
if (const auto params = data.vparams()) {
params->match([&](const MTPDdataJSON &data) {
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(
data.vdata().v,
&error);
if (error.error != QJsonParseError::NoError) {
LOG(("API Error: "
"Failed to parse group call params, error: %1."
).arg(error.errorString()));
return;
} else if (!document.isObject()) {
LOG(("API Error: "
"Not an object received in group call params."));
return;
}
const auto guard = gsl::finally([&] {
addParticipantsToInstance();
});
const auto guard = gsl::finally([&] {
addParticipantsToInstance();
});
if (document.object().value("stream").toBool()) {
if (!streamDcId) {
LOG(("Api Error: Empty stream_dc_id in groupCall."));
}
_broadcastDcId = streamDcId
? streamDcId
: _peer->session().mtp().mainDcId();
setInstanceMode(InstanceMode::Stream);
return;
}
if (document.object().value("stream").toBool()) {
if (!streamDcId) {
LOG(("Api Error: Empty stream_dc_id in groupCall."));
}
_broadcastDcId = streamDcId
? streamDcId
: _peer->session().mtp().mainDcId();
setInstanceMode(InstanceMode::Stream);
return;
}
const auto readString = [](
const QJsonObject &object,
const char *key) {
return object.value(key).toString().toStdString();
};
const auto root = document.object().value("transport").toObject();
auto payload = tgcalls::GroupJoinResponsePayload();
payload.ufrag = readString(root, "ufrag");
payload.pwd = readString(root, "pwd");
const auto prints = root.value("fingerprints").toArray();
const auto candidates = root.value("candidates").toArray();
for (const auto &print : prints) {
const auto object = print.toObject();
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
.hash = readString(object, "hash"),
.setup = readString(object, "setup"),
.fingerprint = readString(object, "fingerprint"),
});
}
for (const auto &candidate : candidates) {
const auto object = candidate.toObject();
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
.port = readString(object, "port"),
.protocol = readString(object, "protocol"),
.network = readString(object, "network"),
.generation = readString(object, "generation"),
.id = readString(object, "id"),
.component = readString(object, "component"),
.foundation = readString(object, "foundation"),
.priority = readString(object, "priority"),
.ip = readString(object, "ip"),
.type = readString(object, "type"),
.tcpType = readString(object, "tcpType"),
.relAddr = readString(object, "relAddr"),
.relPort = readString(object, "relPort"),
});
}
setInstanceMode(InstanceMode::Rtc);
_instance->setJoinResponsePayload(payload, {});
const auto readString = [](
const QJsonObject &object,
const char *key) {
return object.value(key).toString().toStdString();
};
const auto root = document.object().value("transport").toObject();
auto payload = tgcalls::GroupJoinResponsePayload();
payload.ufrag = readString(root, "ufrag");
payload.pwd = readString(root, "pwd");
const auto prints = root.value("fingerprints").toArray();
const auto candidates = root.value("candidates").toArray();
for (const auto &print : prints) {
const auto object = print.toObject();
payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
.hash = readString(object, "hash"),
.setup = readString(object, "setup"),
.fingerprint = readString(object, "fingerprint"),
});
}
}, [&](const MTPDgroupCallDiscarded &data) {
if (data.vid().v == _id) {
_mySsrc = 0;
hangup();
for (const auto &candidate : candidates) {
const auto object = candidate.toObject();
payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
.port = readString(object, "port"),
.protocol = readString(object, "protocol"),
.network = readString(object, "network"),
.generation = readString(object, "generation"),
.id = readString(object, "id"),
.component = readString(object, "component"),
.foundation = readString(object, "foundation"),
.priority = readString(object, "priority"),
.ip = readString(object, "ip"),
.type = readString(object, "type"),
.tcpType = readString(object, "tcpType"),
.relAddr = readString(object, "relAddr"),
.relPort = readString(object, "relPort"),
});
}
setInstanceMode(InstanceMode::Rtc);
_instance->setJoinResponsePayload(payload, {});
});
}
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
@@ -853,7 +865,30 @@ void GroupCall::addPreparedParticipantsDelayed() {
crl::on_main(this, [=] { addPreparedParticipants(); });
}
void GroupCall::handleUpdate(const MTPUpdate &update) {
update.match([&](const MTPDupdateGroupCall &data) {
handleUpdate(data);
}, [&](const MTPDupdateGroupCallParticipants &data) {
handleUpdate(data);
}, [](const auto &) {
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
});
}
void GroupCall::handleUpdate(const MTPDupdateGroupCall &data) {
data.vcall().match([](const MTPDgroupCall &) {
}, [&](const MTPDgroupCallDiscarded &data) {
handlePossibleDiscarded(data);
});
}
void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
const auto callId = data.vcall().match([](const auto &data) {
return data.vid().v;
});
if (_id != callId) {
return;
}
const auto state = _state.current();
if (state != State::Joined && state != State::Connecting) {
return;
@@ -893,18 +928,27 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &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."));
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();
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()) {
@@ -1091,7 +1135,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();
}
@@ -1099,7 +1144,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({
@@ -1312,9 +1358,24 @@ void GroupCall::setInstanceConnected(
if (nowCanSpeak) {
notifyAboutAllowedToSpeak();
}
if (!_hadJoinedState && state() == State::Joined) {
checkFirstTimeJoined();
}
}
void GroupCall::checkFirstTimeJoined() {
if (_hadJoinedState || state() != State::Joined) {
return;
}
_hadJoinedState = true;
applyGlobalShortcutChanges();
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
}
void GroupCall::notifyAboutAllowedToSpeak() {
if (!_hadJoinedState) {
return;
}
_delegate->groupCallPlaySound(
Delegate::GroupCallSound::AllowedToSpeak);
_allowedToSpeakNotifications.fire({});

View File

@@ -116,8 +116,8 @@ public:
void rejoinAs(Group::JoinInfo info);
void rejoinWithHash(const QString &hash);
void join(const MTPInputGroupCall &inputCall);
void handleUpdate(const MTPGroupCall &call);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleUpdate(const MTPUpdate &update);
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void changeTitle(const QString &title);
void toggleRecording(bool enabled, const QString &title);
[[nodiscard]] bool recordingStoppedByMe() const {
@@ -227,6 +227,10 @@ private:
RaiseHand,
};
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);
void handleControllerError(const QString &error);
void ensureControllerCreated();
@@ -250,6 +254,7 @@ private:
void checkGlobalShortcutAvailability();
void checkJoined();
void checkFirstTimeJoined();
void notifyAboutAllowedToSpeak();
void playConnectingSound();
@@ -276,6 +281,7 @@ private:
const not_null<Delegate*> _delegate;
not_null<PeerData*> _peer; // Can change in legacy group migration.
rpl::event_stream<PeerData*> _peerStream;
not_null<History*> _history; // Can change in legacy group migration.
MTP::Sender _api;
rpl::variable<State> _state = State::Creating;
@@ -304,6 +310,7 @@ private:
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _updateMuteRequestId = 0;

View File

@@ -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;
@@ -354,8 +351,6 @@ private:
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
rpl::variable<int> _fullCount = 1;
rpl::variable<int> _fullCountMin = 0;
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
not_null<QWidget*> _menuParent;
base::unique_qptr<Ui::PopupMenu> _menu;
@@ -377,6 +372,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)
@@ -437,32 +452,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.,
@@ -537,7 +550,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);
}
}
}
@@ -662,21 +688,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(
@@ -687,6 +712,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
@@ -701,17 +732,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();
}
@@ -740,11 +788,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,
@@ -823,9 +874,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)),
@@ -954,19 +1003,11 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
delegate()->peerListRefreshRows();
});
if (const auto row = findRow(event.wasJoinAs)) {
if (row->state() != Row::State::Invited) {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
}
removeRow(row);
}
if (findRow(event.nowJoinAs)) {
return;
} else if (auto row = createRowForMe()) {
if (row->state() != Row::State::Invited) {
_fullCountMin = _fullCountMin.current() + 1;
}
delegate()->peerListAppendRow(std::move(row));
}
}, _lifetime);
@@ -976,13 +1017,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
_realCallRawValue = real;
_realId = real->id();
_fullCount = rpl::combine(
real->fullCountValue(),
_fullCountMin.value(),
_fullCountMax.value()
) | rpl::map([](int value, int min, int max) {
return std::max(std::clamp(value, min, max), 1);
});
_fullCount = real->fullCountValue();
real->participantsSliceAdded(
) | rpl::start_with_next([=] {
@@ -1003,9 +1038,6 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
if (isMe(participantPeer)) {
updateRow(row, nullptr);
} else {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
removeRow(row);
delegate()->peerListRefreshRows();
}
@@ -1043,37 +1075,41 @@ void MembersController::appendInvitedUsers() {
void MembersController::updateRow(
const std::optional<Data::GroupCall::Participant> &was,
const Data::GroupCall::Participant &now) {
auto reorderIfInvitedBeforeIndex = 0;
auto countChange = 0;
auto reorderIfInvitedBefore = 0;
auto checkPosition = (Row*)nullptr;
auto addedToBottom = (Row*)nullptr;
if (const auto row = findRow(now.peer)) {
if (row->state() == Row::State::Invited) {
reorderIfInvitedBeforeIndex = row->absoluteIndex();
countChange = 1;
reorderIfInvitedBefore = row->absoluteIndex();
}
updateRow(row, &now);
if ((now.speaking && (!was || !was->speaking))
|| (now.raisedHandRating != (was ? was->raisedHandRating : 0))
|| (!now.canSelfUnmute && was && was->canSelfUnmute)) {
checkRowPosition(row);
checkPosition = row;
}
} else if (auto row = createRow(now)) {
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
reorderIfInvitedBeforeIndex = delegate()->peerListFullRowsCount();
reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
if (now.raisedHandRating != 0) {
checkPosition = row.get();
} else {
addedToBottom = row.get();
}
delegate()->peerListAppendRow(std::move(row));
}
delegate()->peerListRefreshRows();
countChange = 1;
}
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = reorderIfInvitedBeforeIndex;
const auto count = reorderIfInvitedBefore;
if (count <= 0) {
return false;
}
const auto row = delegate()->peerListRowAt(
reorderIfInvitedBeforeIndex - 1).get();
reorderIfInvitedBefore - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
if (reorder) {
@@ -1081,12 +1117,25 @@ void MembersController::updateRow(
return static_cast<const Row&>(row).state() != kInvited;
});
}
if (countChange) {
const auto fullCountMin = _fullCountMin.current() + countChange;
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
if (checkPosition) {
checkRowPosition(checkPosition);
} else if (addedToBottom) {
const auto real = resolvedRealCall();
if (real && real->joinedToTop()) {
const auto proj = [&](const PeerListRow &other) {
const auto &real = static_cast<const Row&>(other);
return real.speaking()
? 2
: (&real == addedToBottom)
? 1
: 0;
};
delegate()->peerListSortRows([&](
const PeerListRow &a,
const PeerListRow &b) {
return proj(a) > proj(b);
});
}
_fullCountMin = fullCountMin;
}
}
@@ -1188,7 +1237,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
// All force muted at the bottom, but 'row' still above others.
? (&real == row.get() ? 1ULL : 0ULL)
// All not force-muted lie between raised hands and speaking.
: (std::numeric_limits<uint64>::max() - 2);
: (kTop - 2);
};
const auto projForOther = [&](const PeerListRow &other) {
const auto &real = static_cast<const Row&>(other);
@@ -1243,11 +1292,6 @@ void MembersController::updateRow(
_soundingAnimation.stop();
}
if (!participant && wasInChat) {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
}
delegate()->peerListUpdateRow(row);
}
@@ -1293,7 +1337,6 @@ void MembersController::prepare() {
; real && call && real->id() == call->id()) {
prepareRows(real);
} else if (auto row = createRowForMe()) {
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
}
@@ -1314,7 +1357,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
auto foundMe = false;
auto changed = false;
const auto &participants = real->participants();
auto fullCountMin = 0;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
@@ -1329,7 +1371,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
participantPeer,
&Data::GroupCall::Participant::peer);
if (contains) {
++fullCountMin;
++i;
} else {
changed = true;
@@ -1348,9 +1389,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
? createRow(*i)
: createRowForMe();
if (row) {
if (row->state() != Row::State::Invited) {
++fullCountMin;
}
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
@@ -1358,20 +1396,12 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
}
for (const auto &participant : participants) {
if (auto row = createRow(participant)) {
++fullCountMin;
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
if (changed) {
delegate()->peerListRefreshRows();
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
if (real->participantsLoaded()) {
_fullCountMax = fullCountMin;
}
}
}
@@ -1426,8 +1456,10 @@ void MembersController::scheduleRaisedHandStatusRemove() {
static_cast<Row*>(row)->clearRaisedHandStatus();
}
i = _raisedHandStatusRemoveAt.erase(i);
} else if (!waiting || waiting > (i->second - now)) {
waiting = i->second - now;
} else {
if (!waiting || waiting > (i->second - now)) {
waiting = i->second - now;
}
++i;
}
}
@@ -1592,7 +1624,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);
});
@@ -1616,7 +1648,11 @@ base::unique_qptr<Ui::PopupMenu> MembersController::createRowContextMenu(
}
} else {
result->addAction(
tr::lng_context_view_profile(tr::now),
(participantPeer->isUser()
? tr::lng_context_view_profile(tr::now)
: participantPeer->isBroadcast()
? tr::lng_context_view_channel(tr::now)
: tr::lng_context_view_group(tr::now)),
showProfile);
if (participantPeer->isUser()) {
result->addAction(
@@ -1625,9 +1661,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()) {
@@ -1635,16 +1669,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()) {

View File

@@ -517,16 +517,10 @@ base::unique_qptr<Ui::Menu::ItemBase> MakeRecordingAction(
base::unique_qptr<Ui::Menu::ItemBase> MakeFinishAction(
not_null<Ui::Menu::Menu*> menu,
Fn<void()> callback) {
return base::make_unique_q<Ui::Menu::Action>(
return MakeAttentionAction(
menu,
st::groupCallFinishMenu,
Ui::Menu::CreateAction(
menu,
tr::lng_group_call_end(tr::now),
std::move(callback)),
nullptr,
nullptr);
tr::lng_group_call_end(tr::now),
std::move(callback));
}
} // namespace
@@ -675,6 +669,21 @@ void FillMenu(
BoxContext::GroupCallPanel));
}
}));
}
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);
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace Ui {
class DropdownMenu;
@@ -15,6 +16,11 @@ class GenericBox;
class BoxContent;
} // namespace Ui
namespace Ui::Menu {
class ItemBase;
class Menu;
} // namespace Ui::Menu
namespace Calls {
class GroupCall;
} // namespace Calls
@@ -45,4 +51,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

View File

@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/layer_manager.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/special_buttons.h"
#include "info/profile/info_profile_values.h" // Info::Profile::Value.
@@ -82,7 +81,7 @@ private:
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const override;
not_null<PeerData*> participant) const override;
not_null<PeerData*> _peer;
const base::flat_set<not_null<UserData*>> _alreadyIn;
@@ -190,8 +189,9 @@ bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
}
std::unique_ptr<PeerListRow> InviteController::createRow(
not_null<UserData*> user) const {
if (user->isSelf() || user->isBot()) {
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (!user || user->isSelf() || user->isBot()) {
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
@@ -291,9 +291,10 @@ Panel::Panel(not_null<GroupCall*> call)
call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (isActive()) {
Ui::Toast::Show(
widget(),
tr::lng_group_call_can_speak_here(tr::now));
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_can_speak_here(tr::now) },
});
} else {
const auto real = _peer->groupCall();
const auto name = (real
@@ -301,13 +302,12 @@ Panel::Panel(not_null<GroupCall*> call)
&& !real->title().isEmpty())
? real->title()
: _peer->name;
Ui::Toast::Show(Ui::Toast::Config{
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
}
}, widget()->lifetime());
@@ -476,7 +476,7 @@ void Panel::initControls() {
}
});
_settings->setText(tr::lng_menu_settings());
_settings->setText(tr::lng_group_call_settings());
_hangup->setText(tr::lng_group_call_leave());
_members->desiredHeightValue(
@@ -529,16 +529,17 @@ void Panel::initWithCall(GroupCall *call) {
_members->kickParticipantRequests(
) | rpl::start_with_next([=](not_null<PeerData*> participantPeer) {
if (const auto user = participantPeer->asUser()) {
kickMember(user);
}
kickParticipant(participantPeer);
}, _callLifetime);
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::Toast::Show(widget(), text);
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
@@ -659,9 +660,10 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto skip = st::groupCallRecordingMarkSkip;
_recordingMark->resize(size + 2 * skip, size + 2 * skip);
_recordingMark->setClickedCallback([=] {
Ui::Toast::Show(
widget(),
tr::lng_group_call_is_recorded(tr::now));
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_is_recorded(tr::now) },
});
});
const auto animate = [=] {
const auto opaque = state->opaque;
@@ -697,13 +699,16 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool recorded) {
validateRecordingMark(recorded);
Ui::Toast::Show(
widget(),
(recorded
? tr::lng_group_call_recording_started(tr::now)
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = (recorded
? tr::lng_group_call_recording_started
: (_call && _call->recordingStoppedByMe())
? tr::lng_group_call_recording_saved(tr::now)
: tr::lng_group_call_recording_stopped(tr::now)));
? tr::lng_group_call_recording_saved
: tr::lng_group_call_recording_stopped)(
tr::now,
Ui::Text::RichLangValue),
});
}, widget()->lifetime());
validateRecordingMark(real->recordStartDate() != 0);
@@ -755,7 +760,10 @@ void Panel::chooseJoinAs() {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::Toast::Show(widget(), text);
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
_joinAsProcess.start(
_peer,
@@ -849,28 +857,24 @@ void Panel::addMembers() {
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
});
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");
@@ -954,15 +958,22 @@ void Panel::addMembers() {
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
}
void Panel::kickMember(not_null<UserData*> user) {
void Panel::kickParticipant(not_null<PeerData*> participantPeer) {
_layerBg->showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_profile_sure_kick(
tr::now,
lt_user,
user->firstName),
(!participantPeer->isUser()
? tr::lng_group_call_remove_channel(
tr::now,
lt_channel,
participantPeer->name)
: (_peer->isBroadcast()
? tr::lng_profile_sure_kick_channel
: tr::lng_profile_sure_kick)(
tr::now,
lt_user,
participantPeer->asUser()->firstName)),
st::groupCallBoxLabel),
style::margins(
st::boxRowPadding.left(),
@@ -971,26 +982,29 @@ void Panel::kickMember(not_null<UserData*> user) {
st::boxPadding.bottom()));
box->addButton(tr::lng_box_remove(), [=] {
box->closeBox();
kickMemberSure(user);
kickParticipantSure(participantPeer);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}));
}
void Panel::kickMemberSure(not_null<UserData*> user) {
void Panel::kickParticipantSure(not_null<PeerData*> participantPeer) {
if (const auto chat = _peer->asChat()) {
chat->session().api().kickParticipant(chat, user);
chat->session().api().kickParticipant(chat, participantPeer);
} else if (const auto channel = _peer->asChannel()) {
const auto currentRestrictedRights = [&]() -> MTPChatBannedRights {
const auto it = channel->mgInfo->lastRestricted.find(user);
return (it != channel->mgInfo->lastRestricted.cend())
? it->second.rights
: MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
const auto currentRestrictedRights = [&] {
const auto user = participantPeer->asUser();
if (!channel->mgInfo || !user) {
return ChannelData::EmptyRestrictedRights(participantPeer);
}
const auto i = channel->mgInfo->lastRestricted.find(user);
return (i != channel->mgInfo->lastRestricted.cend())
? i->second.rights
: ChannelData::EmptyRestrictedRights(participantPeer);
}();
channel->session().api().kickParticipant(
channel,
user,
participantPeer,
currentRestrictedRights);
}
}
@@ -1132,7 +1146,9 @@ void Panel::refreshTitle() {
widget(),
tr::lng_group_call_members(
lt_count_decimal,
_members->fullCountValue() | tr::to_count()),
_members->fullCountValue() | rpl::map([](int value) {
return (value > 0) ? float64(value) : 1.;
})),
st::groupCallSubtitleLabel);
_subtitle->show();
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);

View File

@@ -89,8 +89,8 @@ private:
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();

View File

@@ -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"
@@ -535,9 +535,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 +573,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;
};

View File

@@ -323,9 +323,9 @@ void Instance::handleUpdate(
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
handleSignalingData(session, data);
}, [&](const MTPDupdateGroupCall &data) {
handleGroupCallUpdate(session, data.vcall());
handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallParticipants &data) {
handleGroupCallUpdate(session, data);
handleGroupCallUpdate(session, update);
}, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate.");
});
@@ -410,31 +410,38 @@ void Instance::handleCallUpdate(
void Instance::handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPGroupCall &call) {
const auto callId = call.match([](const auto &data) {
return data.vid().v;
const MTPUpdate &update) {
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
return data.vcall().match([](const auto &data) {
return data.vid().v;
});
}, [](const MTPDupdateGroupCallParticipants &data) {
return data.vcall().match([&](const MTPDinputGroupCall &data) {
return data.vid().v;
});
}, [](const auto &) -> uint64 {
Unexpected("Type in Instance::handleGroupCallUpdate.");
});
if (const auto existing = session->data().groupCall(callId)) {
existing->applyUpdate(call);
existing->enqueueUpdate(update);
} else {
applyGroupCallUpdateChecked(session, update);
}
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(call);
update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
}, [](const auto &) {
});
}
}
void Instance::handleGroupCallUpdate(
void Instance::applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPDupdateGroupCallParticipants &update) {
const auto callId = update.vcall().match([](const auto &data) {
return data.vid().v;
});
if (const auto existing = session->data().groupCall(callId)) {
existing->applyUpdate(update);
}
const MTPUpdate &update) {
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)
&& (_currentGroupCall->id() == callId)) {
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(update);
}
}

View File

@@ -50,6 +50,12 @@ public:
void handleUpdate(
not_null<Main::Session*> session,
const MTPUpdate &update);
// Called by Data::GroupCall when it is appropriate by the 'version'.
void applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPUpdate &update);
void showInfoPanel(not_null<Call*> call);
void showInfoPanel(not_null<GroupCall*> call);
[[nodiscard]] Call *currentCall() const;
@@ -130,10 +136,7 @@ private:
const MTPDupdatePhoneCallSignalingData &data);
void handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPGroupCall &call);
void handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPDupdateGroupCallParticipants &update);
const MTPUpdate &update);
DhConfig _dhConfig;

View File

@@ -625,8 +625,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 +634,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);
}

View File

@@ -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

View File

@@ -127,6 +127,26 @@ std::map<int, const char*> BetaLogs() {
"- Fix voice chat admin menu on macOS.\n"
},
{
2006004,
"- Fix freeze in voice chats.\n"
"- Make default interface scale 110% on macOS Retina screens.\n"
},
{
2006005,
"- Improvements and fixes in new voice chat features.\n"
},
{
2006007,
"- Improve voice chat participants list updating.\n"
},
{
2006008,
"- Fix connecting and getting allowed to speak on voice chats.\n"
"- MPRIS support on Linux.\n"
},
};
};

View File

@@ -108,7 +108,7 @@ void ComputeExternalUpdater() {
while (!fileStream.atEnd()) {
const auto path = fileStream.readLine();
if (path == (cWorkingDir() + cExeName())) {
if (path == (cExeDir() + cExeName())) {
SetUpdaterDisabledAtStartup();
return;
}

View File

@@ -210,7 +210,7 @@ void Sandbox::setupScreenScale() {
}
style::SetDevicePixelRatio(int(ratio));
if (Platform::IsMac() && ratio == 2.) {
cSetScreenScale(120); // 120% for Retina screens by default.
cSetScreenScale(110); // 110% for Retina screens by default.
} else {
cSetScreenScale(style::kScaleDefault);
}

View File

@@ -126,6 +126,10 @@ QString UiIntegration::timeFormat() {
return cTimeFormat();
}
QWidget *UiIntegration::modalWindowParent() {
return Core::App().getModalParent();
}
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
const EntityLinkData &data,
const std::any &context) {

View File

@@ -46,6 +46,8 @@ public:
void startFontsEnd() override;
QString timeFormat() override;
QWidget *modalWindowParent() override;
std::shared_ptr<ClickHandler> createLinkHandler(
const EntityLinkData &data,
const std::any &context) override;

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 2006003;
constexpr auto AppVersionStr = "2.6.3";
constexpr auto AppBetaVersion = true;
constexpr auto AppVersion = 2007001;
constexpr auto AppVersionStr = "2.7.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -188,7 +188,13 @@ void ChannelData::setKickedCount(int newKickedCount) {
}
}
MTPChatBannedRights ChannelData::KickedRestrictedRights() {
MTPChatBannedRights ChannelData::EmptyRestrictedRights(
not_null<PeerData*> participant) {
return MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
}
MTPChatBannedRights ChannelData::KickedRestrictedRights(
not_null<PeerData*> participant) {
using Flag = MTPDchatBannedRights::Flag;
const auto flags = Flag::f_view_messages
| Flag::f_send_messages
@@ -199,7 +205,7 @@ MTPChatBannedRights ChannelData::KickedRestrictedRights() {
| Flag::f_send_games
| Flag::f_send_inline;
return MTP_chatBannedRights(
MTP_flags(flags),
MTP_flags(participant->isUser() ? flags : Flag::f_view_messages),
MTP_int(std::numeric_limits<int32>::max()));
}
@@ -267,11 +273,17 @@ void ChannelData::applyEditAdmin(
session().changes().peerUpdated(this, UpdateFlag::Admins);
}
void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedRights &oldRights, const MTPChatBannedRights &newRights) {
void ChannelData::applyEditBanned(
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights) {
auto flags = UpdateFlag::BannedUsers | UpdateFlag::None;
auto isKicked = (newRights.c_chatBannedRights().vflags().v & MTPDchatBannedRights::Flag::f_view_messages);
auto isRestricted = !isKicked && (newRights.c_chatBannedRights().vflags().v != 0);
if (mgInfo) {
auto isKicked = Data::ChatBannedRightsFlags(newRights)
& ChatRestriction::f_view_messages;
auto isRestricted = !isKicked
&& (Data::ChatBannedRightsFlags(newRights) != 0);
const auto user = participant->asUser();
if (mgInfo && user) {
// If rights are empty - still remove admin? TODO check
if (mgInfo->lastAdmins.contains(user)) {
mgInfo->lastAdmins.remove(user);
@@ -284,7 +296,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
auto it = mgInfo->lastRestricted.find(user);
if (isRestricted) {
if (it == mgInfo->lastRestricted.cend()) {
mgInfo->lastRestricted.emplace(user, MegagroupInfo::Restricted { newRights });
mgInfo->lastRestricted.emplace(
user,
MegagroupInfo::Restricted { newRights });
setRestrictedCount(restrictedCount() + 1);
} else {
it->second.rights = newRights;
@@ -297,7 +311,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
}
}
if (isKicked) {
auto i = ranges::find(mgInfo->lastParticipants, user);
auto i = ranges::find(
mgInfo->lastParticipants,
not_null{ user });
if (i != mgInfo->lastParticipants.end()) {
mgInfo->lastParticipants.erase(i);
}
@@ -319,9 +335,9 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChatBannedR
}
}
Data::ChannelAdminChanges(this).remove(peerToUser(user->id));
} else {
} else if (!mgInfo) {
if (isKicked) {
if (membersCount() > 1) {
if (user && membersCount() > 1) {
setMembersCount(membersCount() - 1);
flags |= UpdateFlag::Members;
}
@@ -484,7 +500,7 @@ bool ChannelData::canDelete() const {
}
bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canEditAdmin :(
// Duplicated in ParticipantsAdditionalData::canEditAdmin :(
if (mgInfo) {
auto i = mgInfo->lastAdmins.find(user);
if (i != mgInfo->lastAdmins.cend()) {
@@ -496,7 +512,7 @@ bool ChannelData::canEditLastAdmin(not_null<UserData*> user) const {
}
bool ChannelData::canEditAdmin(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canEditAdmin :(
// Duplicated in ParticipantsAdditionalData::canEditAdmin :(
if (user->isSelf()) {
return false;
} else if (amCreator()) {
@@ -507,14 +523,17 @@ bool ChannelData::canEditAdmin(not_null<UserData*> user) const {
return adminRights() & AdminRight::f_add_admins;
}
bool ChannelData::canRestrictUser(not_null<UserData*> user) const {
// Duplicated in ParticipantsBoxController::canRestrictUser :(
if (user->isSelf()) {
bool ChannelData::canRestrictParticipant(
not_null<PeerData*> participant) const {
// Duplicated in ParticipantsAdditionalData::canRestrictParticipant :(
if (participant->isSelf()) {
return false;
} else if (amCreator()) {
return true;
} else if (!canEditLastAdmin(user)) {
return false;
} else if (const auto user = participant->asUser()) {
if (!canEditLastAdmin(user)) {
return false;
}
}
return adminRights() & AdminRight::f_ban_users;
}
@@ -543,12 +562,14 @@ void ChannelData::setAdminRights(const MTPChatAdminRights &rights) {
}
void ChannelData::setRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == restrictions()
&& rights.c_chatBannedRights().vuntil_date().v == _restrictedUntil) {
const auto restrictedFlags = Data::ChatBannedRightsFlags(rights);
const auto restrictedUntilDate = Data::ChatBannedRightsUntilDate(rights);
if (restrictedFlags == restrictions()
&& restrictedUntilDate == _restrictedUntil) {
return;
}
_restrictedUntil = rights.c_chatBannedRights().vuntil_date().v;
_restrictions.set(rights.c_chatBannedRights().vflags().v);
_restrictedUntil = restrictedUntilDate;
_restrictions.set(restrictedFlags);
if (isMegagroup()) {
const auto self = session().user();
if (hasRestrictions()) {
@@ -568,10 +589,11 @@ void ChannelData::setRestrictions(const MTPChatBannedRights &rights) {
}
void ChannelData::setDefaultRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == defaultRestrictions()) {
const auto restrictionFlags = Data::ChatBannedRightsFlags(rights);
if (restrictionFlags == defaultRestrictions()) {
return;
}
_defaultRestrictions.set(rights.c_chatBannedRights().vflags().v);
_defaultRestrictions.set(restrictionFlags);
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
@@ -903,8 +925,13 @@ void ApplyMegagroupAdmins(
auto admins = ranges::make_subrange(
list.begin(), list.end()
) | ranges::views::transform([](const MTPChannelParticipant &p) {
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 rank = p.match([](const MTPDchannelParticipantAdmin &data) {
return qs(data.vrank().value_or_empty());
@@ -913,10 +940,13 @@ void ApplyMegagroupAdmins(
}, [](const auto &data) {
return QString();
});
return std::make_pair(userId, rank);
return std::make_pair(participantId, rank);
}) | ranges::views::filter([](const auto &pair) {
return peerIsUser(pair.first);
});
for (const auto &[userId, rank] : admins) {
adding.emplace(userId, rank);
for (const auto &[participantId, rank] : admins) {
Assert(peerIsUser(participantId));
adding.emplace(peerToUser(participantId), rank);
}
if (channel->mgInfo->creator) {
adding.emplace(

View File

@@ -209,7 +209,10 @@ public:
return flags() & MTPDchannel::Flag::f_fake;
}
static MTPChatBannedRights KickedRestrictedRights();
static MTPChatBannedRights EmptyRestrictedRights(
not_null<PeerData*> participant);
static MTPChatBannedRights KickedRestrictedRights(
not_null<PeerData*> participant);
static constexpr auto kRestrictUntilForever = TimeId(INT_MAX);
[[nodiscard]] static bool IsRestrictedForever(TimeId until) {
return !until || (until == kRestrictUntilForever);
@@ -220,7 +223,7 @@ public:
const MTPChatAdminRights &newRights,
const QString &rank);
void applyEditBanned(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights);
@@ -310,7 +313,8 @@ public:
[[nodiscard]] bool canEditStickers() const;
[[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant(
not_null<PeerData*> participant) const;
void setInviteLink(const QString &newInviteLink);
[[nodiscard]] QString inviteLink() const {

View File

@@ -52,7 +52,8 @@ auto ChatData::defaultAdminRights(not_null<UserData*> user) -> AdminRights {
const auto isCreator = (creator == user->bareId())
|| (user->isSelf() && amCreator());
using Flag = AdminRight;
return Flag::f_change_info
return Flag::f_other
| Flag::f_change_info
| Flag::f_delete_messages
| Flag::f_ban_users
| Flag::f_invite_users
@@ -155,10 +156,11 @@ void ChatData::setAdminRights(const MTPChatAdminRights &rights) {
}
void ChatData::setDefaultRestrictions(const MTPChatBannedRights &rights) {
if (rights.c_chatBannedRights().vflags().v == defaultRestrictions()) {
const auto restrictionFlags = Data::ChatBannedRightsFlags(rights);
if (restrictionFlags == defaultRestrictions()) {
return;
}
_defaultRestrictions.set(rights.c_chatBannedRights().vflags().v);
_defaultRestrictions.set(restrictionFlags);
session().changes().peerUpdated(this, UpdateFlag::Rights);
}

View File

@@ -22,9 +22,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
namespace {
constexpr auto kRequestPerPage = 30;
constexpr auto kRequestPerPage = 50;
constexpr auto kSpeakingAfterActive = crl::time(6000);
constexpr auto kActiveAfterJoined = crl::time(1000);
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
[[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) {
return call.match([&](const MTPDphone_groupCall &data) {
return qs(data.vparticipants_next_offset());
});
}
} // namespace
@@ -35,6 +42,7 @@ GroupCall::GroupCall(
: _id(id)
, _accessHash(accessHash)
, _peer(peer)
, _reloadByQueuedUpdatesTimer([=] { reload(); })
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
}
@@ -48,6 +56,10 @@ uint64 GroupCall::id() const {
return _id;
}
bool GroupCall::loaded() const {
return _version > 0;
}
not_null<PeerData*> GroupCall::peer() const {
return _peer;
}
@@ -69,21 +81,24 @@ auto GroupCall::participants() const
}
void GroupCall::requestParticipants() {
if (_participantsRequestId || _reloadRequestId) {
return;
} else if (_participants.size() >= _fullCount.current() && _allReceived) {
return;
} else if (_allReceived) {
reload();
return;
if (!_savedFull) {
if (_participantsRequestId || _reloadRequestId) {
return;
} else if (_allParticipantsLoaded) {
return;
}
}
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
input(),
MTP_vector<MTPInputPeer>(), // ids
MTP_vector<MTPint>(), // ssrcs
MTP_string(_nextOffset),
MTP_string(_savedFull
? ExtractNextOffset(*_savedFull)
: _nextOffset),
MTP_int(kRequestPerPage)
)).done([=](const MTPphone_GroupParticipants &result) {
_participantsRequestId = 0;
processSavedFullCall();
result.match([&](const MTPDphone_groupParticipants &data) {
_nextOffset = qs(data.vnext_offset());
_peer->owner().processUsers(data.vusers());
@@ -91,26 +106,40 @@ void GroupCall::requestParticipants() {
applyParticipantsSlice(
data.vparticipants().v,
ApplySliceSource::SliceLoaded);
_fullCount = data.vcount().v;
if (!_allReceived
&& (data.vparticipants().v.size() < kRequestPerPage)) {
_allReceived = true;
}
if (_allReceived) {
_fullCount = _participants.size();
setServerParticipantsCount(data.vcount().v);
if (data.vparticipants().v.isEmpty()) {
_allParticipantsLoaded = true;
}
finishParticipantsSliceRequest();
});
_participantsSliceAdded.fire({});
_participantsRequestId = 0;
changePeerEmptyCallFlag();
}).fail([=](const MTP::Error &error) {
_fullCount = _participants.size();
_allReceived = true;
_participantsRequestId = 0;
changePeerEmptyCallFlag();
processSavedFullCall();
setServerParticipantsCount(_participants.size());
_allParticipantsLoaded = true;
finishParticipantsSliceRequest();
}).send();
}
void GroupCall::processSavedFullCall() {
if (!_savedFull) {
return;
}
_reloadRequestId = 0;
processFullCallFields(*base::take(_savedFull));
}
void GroupCall::finishParticipantsSliceRequest() {
computeParticipantsCount();
processQueuedUpdates();
_participantsSliceAdded.fire({});
}
void GroupCall::setServerParticipantsCount(int count) {
_serverParticipantsCount = count;
changePeerEmptyCallFlag();
}
void GroupCall::changePeerEmptyCallFlag() {
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
@@ -118,7 +147,7 @@ void GroupCall::changePeerEmptyCallFlag() {
constexpr auto channelFlag = MTPDchannel::Flag::f_call_not_empty;
if (_peer->groupCall() != this) {
return;
} else if (fullCount() > 0) {
} else if (_serverParticipantsCount > 0) {
if (chat && !(chat->flags() & chatFlag)) {
chat->addFlags(chatFlag);
chat->session().changes().peerUpdated(
@@ -152,7 +181,7 @@ rpl::producer<int> GroupCall::fullCountValue() const {
}
bool GroupCall::participantsLoaded() const {
return _allReceived;
return _allParticipantsLoaded;
}
PeerData *GroupCall::participantPeerBySsrc(uint32 ssrc) const {
@@ -169,82 +198,269 @@ auto GroupCall::participantUpdated() const
return _participantUpdates.events();
}
void GroupCall::applyUpdate(const MTPGroupCall &update) {
applyCall(update, false);
void GroupCall::enqueueUpdate(const MTPUpdate &update) {
update.match([&](const MTPDupdateGroupCall &updateData) {
updateData.vcall().match([&](const MTPDgroupCall &data) {
const auto version = data.vversion().v;
if (!_applyingQueuedUpdates
&& (!_version || _version == version)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
applyEnqueuedUpdate(update);
} else if (!_version || _version <= version) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCall %1 -> %2"
).arg(_version
).arg(version));
const auto type = QueuedType::Call;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [&](const MTPDgroupCallDiscarded &data) {
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
const auto version = updateData.vversion().v;
const auto proj = [](const MTPGroupCallParticipant &data) {
return data.match([&](const MTPDgroupCallParticipant &data) {
return data.is_versioned();
});
};
const auto increment = ranges::contains(
updateData.vparticipants().v,
true,
proj);
const auto required = increment ? (version - 1) : version;
if (!_applyingQueuedUpdates && (_version == required)) {
DEBUG_LOG(("Group Call Participants: "
"Apply updateGroupCallParticipant %1 (%2)"
).arg(_version
).arg(Logs::b(increment)));
applyEnqueuedUpdate(update);
} else if (_version <= required) {
DEBUG_LOG(("Group Call Participants: "
"Queue updateGroupCallParticipant %1 -> %2 (%3)"
).arg(_version
).arg(version
).arg(Logs::b(increment)));
const auto type = increment
? QueuedType::VersionedParticipant
: QueuedType::Participant;
_queuedUpdates.emplace(std::pair{ version, type }, update);
}
}, [](const auto &) {
Unexpected("Type in GroupCall::enqueueUpdate.");
});
processQueuedUpdates();
}
void GroupCall::applyCall(const MTPGroupCall &call, bool force) {
call.match([&](const MTPDgroupCall &data) {
if (!_version) {
_version = data.vversion().v;
}
const auto title = qs(data.vtitle().value_or_empty());
const auto recordDate = data.vrecord_start_date().value_or_empty();
const auto changed = (_joinMuted != data.is_join_muted())
|| (_fullCount.current() != data.vparticipants_count().v)
|| (_canChangeJoinMuted != data.is_can_change_join_muted())
|| (_title.current() != title)
|| (_recordStartDate.current() != recordDate);
if (!force && !changed) {
return;
} else if (!force && _version > data.vversion().v) {
reload();
return;
}
_joinMuted = data.is_join_muted();
_canChangeJoinMuted = data.is_can_change_join_muted();
_fullCount = data.vparticipants_count().v;
_title = title;
_recordStartDate = recordDate;
changePeerEmptyCallFlag();
}, [&](const MTPDgroupCallDiscarded &data) {
const auto id = _id;
const auto peer = _peer;
crl::on_main(&peer->session(), [=] {
if (peer->groupCall() && peer->groupCall()->id() == id) {
if (const auto chat = peer->asChat()) {
chat->clearGroupCall();
} else if (const auto channel = peer->asChannel()) {
channel->clearGroupCall();
}
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
const auto id = _id;
const auto peer = _peer;
crl::on_main(&peer->session(), [=] {
if (peer->groupCall() && peer->groupCall()->id() == id) {
if (const auto chat = peer->asChat()) {
chat->clearGroupCall();
} else if (const auto channel = peer->asChannel()) {
channel->clearGroupCall();
}
}
});
Core::App().calls().applyGroupCallUpdateChecked(
&peer->session(),
MTP_updateGroupCall(
MTP_int(peer->bareId()),
MTP_groupCallDiscarded(
data.vid(),
data.vaccess_hash(),
data.vduration())));
}
void GroupCall::processFullCallUsersChats(const MTPphone_GroupCall &call) {
call.match([&](const MTPDphone_groupCall &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
});
}
void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) {
call.match([&](const MTPDphone_groupCall &data) {
const auto &participants = data.vparticipants().v;
const auto nextOffset = qs(data.vparticipants_next_offset());
data.vcall().match([&](const MTPDgroupCall &data) {
_participants.clear();
_speakingByActiveFinishes.clear();
_participantPeerBySsrc.clear();
_allParticipantsLoaded = false;
applyParticipantsSlice(
participants,
ApplySliceSource::SliceLoaded);
_nextOffset = nextOffset;
applyCallFields(data);
}, [&](const MTPDgroupCallDiscarded &data) {
discard(data);
});
});
}
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
call.match([&](const MTPDphone_groupCall &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
_participants.clear();
_speakingByActiveFinishes.clear();
_participantPeerBySsrc.clear();
processFullCallUsersChats(call);
processFullCallFields(call);
finishParticipantsSliceRequest();
}
void GroupCall::applyCallFields(const MTPDgroupCall &data) {
DEBUG_LOG(("Group Call Participants: "
"Set from groupCall %1 -> %2"
).arg(_version
).arg(data.vversion().v));
_version = data.vversion().v;
if (!_version) {
LOG(("API Error: Got zero version in groupCall."));
_version = 1;
}
_joinMuted = data.is_join_muted();
_canChangeJoinMuted = data.is_can_change_join_muted();
_joinedToTop = !data.is_join_date_asc();
setServerParticipantsCount(data.vparticipants_count().v);
changePeerEmptyCallFlag();
_title = qs(data.vtitle().value_or_empty());
_recordStartDate = data.vrecord_start_date().value_or_empty();
_allParticipantsLoaded
= (_serverParticipantsCount == _participants.size());
}
void GroupCall::applyLocalUpdate(
const MTPDupdateGroupCallParticipants &update) {
applyParticipantsSlice(
update.vparticipants().v,
ApplySliceSource::UpdateReceived);
}
void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) {
Expects(!_applyingQueuedUpdates);
_applyingQueuedUpdates = true;
const auto guard = gsl::finally([&] { _applyingQueuedUpdates = false; });
update.match([&](const MTPDupdateGroupCall &data) {
data.vcall().match([&](const MTPDgroupCall &data) {
applyCallFields(data);
computeParticipantsCount();
}, [&](const MTPDgroupCallDiscarded &data) {
discard(data);
});
}, [&](const MTPDupdateGroupCallParticipants &data) {
DEBUG_LOG(("Group Call Participants: "
"Set from updateGroupCallParticipants %1 -> %2"
).arg(_version
).arg(data.vversion().v));
_version = data.vversion().v;
if (!_version) {
LOG(("API Error: "
"Got zero version in updateGroupCallParticipants."));
_version = 1;
}
applyParticipantsSlice(
data.vparticipants().v,
ApplySliceSource::SliceLoaded);
applyCall(data.vcall(), true);
_allReceived = (_fullCount.current() == _participants.size());
_participantsSliceAdded.fire({});
ApplySliceSource::UpdateReceived);
}, [](const auto &) {
Unexpected("Type in GroupCall::applyEnqueuedUpdate.");
});
Core::App().calls().applyGroupCallUpdateChecked(
&_peer->session(),
update);
}
void GroupCall::processQueuedUpdates() {
if (!_version || _applyingQueuedUpdates) {
return;
}
const auto size = _queuedUpdates.size();
while (!_queuedUpdates.empty()) {
const auto &entry = _queuedUpdates.front();
const auto version = entry.first.first;
const auto type = entry.first.second;
const auto incremented = (type == QueuedType::VersionedParticipant);
if ((version < _version)
|| (version == _version && incremented)) {
_queuedUpdates.erase(_queuedUpdates.begin());
} else if (version == _version
|| (version == _version + 1 && incremented)) {
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyEnqueuedUpdate(update);
} else {
break;
}
}
if (_queuedUpdates.empty()) {
_reloadByQueuedUpdatesTimer.cancel();
} else if (_queuedUpdates.size() != size
|| !_reloadByQueuedUpdatesTimer.isActive()) {
_reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout);
}
}
void GroupCall::computeParticipantsCount() {
_fullCount = _allParticipantsLoaded
? int(_participants.size())
: std::max(int(_participants.size()), _serverParticipantsCount);
}
void GroupCall::reload() {
if (_reloadRequestId) {
if (_reloadRequestId || _applyingQueuedUpdates) {
return;
} else if (_participantsRequestId) {
api().request(_participantsRequestId).cancel();
_participantsRequestId = 0;
}
DEBUG_LOG(("Group Call Participants: "
"Reloading with queued: %1"
).arg(_queuedUpdates.size()));
while (!_queuedUpdates.empty()) {
const auto &entry = _queuedUpdates.front();
const auto update = entry.second;
_queuedUpdates.erase(_queuedUpdates.begin());
applyEnqueuedUpdate(update);
}
_reloadByQueuedUpdatesTimer.cancel();
_reloadRequestId = api().request(
MTPphone_GetGroupCall(input())
).done([=](const MTPphone_GroupCall &result) {
processFullCall(result);
if (requestParticipantsAfterReload(result)) {
_savedFull = result;
processFullCallUsersChats(result);
requestParticipants();
return;
}
_reloadRequestId = 0;
processFullCall(result);
}).fail([=](const MTP::Error &error) {
_reloadRequestId = 0;
}).send();
}
bool GroupCall::requestParticipantsAfterReload(
const MTPphone_GroupCall &call) const {
return call.match([&](const MTPDphone_groupCall &data) {
const auto received = data.vparticipants().v.size();
const auto size = data.vcall().match([&](const MTPDgroupCall &data) {
return data.vparticipants_count().v;
}, [](const auto &) {
return 0;
});
return (received < size) && (received < _participants.size());
});
}
void GroupCall::applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
ApplySliceSource sliceSource) {
@@ -252,7 +468,6 @@ void GroupCall::applyParticipantsSlice(
const auto now = base::unixtime::now();
const auto speakingAfterActive = TimeId(kSpeakingAfterActive / 1000);
auto changedCount = _fullCount.current();
for (const auto &participant : list) {
participant.match([&](const MTPDgroupCallParticipant &data) {
const auto participantPeerId = peerFromMTP(data.vpeer());
@@ -274,8 +489,8 @@ void GroupCall::applyParticipantsSlice(
_participantUpdates.fire(std::move(update));
}
}
if (changedCount > _participants.size()) {
--changedCount;
if (_serverParticipantsCount > 0) {
--_serverParticipantsCount;
}
return;
}
@@ -338,7 +553,7 @@ void GroupCall::applyParticipantsSlice(
*i = value;
}
if (data.is_just_joined()) {
++changedCount;
++_serverParticipantsCount;
}
if (sliceSource != ApplySliceSource::SliceLoaded) {
_participantUpdates.fire({
@@ -349,8 +564,8 @@ void GroupCall::applyParticipantsSlice(
});
}
if (sliceSource == ApplySliceSource::UpdateReceived) {
_fullCount = changedCount;
changePeerEmptyCallFlag();
computeParticipantsCount();
}
}
@@ -444,7 +659,7 @@ void GroupCall::applyActiveUpdate(
void GroupCall::checkFinishSpeakingByActive() {
const auto now = crl::now();
auto nearest = 0;
auto nearest = crl::time(0);
auto stop = std::vector<not_null<PeerData*>>();
for (auto i = begin(_speakingByActiveFinishes)
; i != end(_speakingByActiveFinishes);) {
@@ -542,6 +757,7 @@ void GroupCall::requestUnknownParticipants() {
).done([=](const MTPphone_GroupParticipants &result) {
result.match([&](const MTPDphone_groupParticipants &data) {
_peer->owner().processUsers(data.vusers());
_peer->owner().processChats(data.vchats());
applyParticipantsSlice(
data.vparticipants().v,
ApplySliceSource::UnknownLoaded);
@@ -605,41 +821,6 @@ bool GroupCall::inCall() const {
&& (current->state() == Calls::GroupCall::State::Joined);
}
void GroupCall::applyUpdate(const MTPDupdateGroupCallParticipants &update) {
const auto version = update.vversion().v;
const auto applyUpdate = [&] {
if (version < _version) {
return false;
}
auto versionShouldIncrement = false;
for (const auto &participant : update.vparticipants().v) {
const auto versioned = participant.match([&](
const MTPDgroupCallParticipant &data) {
return data.is_versioned();
});
if (versioned) {
versionShouldIncrement = true;
break;
}
}
return versionShouldIncrement
? (version == _version + 1)
: (version == _version);
}();
if (!applyUpdate) {
return;
}
_version = version;
applyUpdateChecked(update);
}
void GroupCall::applyUpdateChecked(
const MTPDupdateGroupCallParticipants &update) {
applyParticipantsSlice(
update.vparticipants().v,
ApplySliceSource::UpdateReceived);
}
void GroupCall::setJoinMutedLocally(bool muted) {
_joinMuted = muted;
}
@@ -652,6 +833,10 @@ bool GroupCall::canChangeJoinMuted() const {
return _canChangeJoinMuted;
}
bool GroupCall::joinedToTop() const {
return _joinedToTop;
}
ApiWrap &GroupCall::api() const {
return _peer->session().api();
}

View File

@@ -42,6 +42,7 @@ public:
~GroupCall();
[[nodiscard]] uint64 id() const;
[[nodiscard]] bool loaded() const;
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] MTPInputGroupCall input() const;
[[nodiscard]] QString title() const {
@@ -82,10 +83,10 @@ public:
[[nodiscard]] rpl::producer<> participantsSliceAdded();
[[nodiscard]] rpl::producer<ParticipantUpdate> participantUpdated() const;
void applyUpdate(const MTPGroupCall &update);
void applyUpdate(const MTPDupdateGroupCallParticipants &update);
void applyUpdateChecked(
void enqueueUpdate(const MTPUpdate &update);
void applyLocalUpdate(
const MTPDupdateGroupCallParticipants &update);
void applyLastSpoke(uint32 ssrc, LastSpokeTimes when, crl::time now);
void applyActiveUpdate(
PeerId participantPeerId,
@@ -104,6 +105,7 @@ public:
void setJoinMutedLocally(bool muted);
[[nodiscard]] bool joinMuted() const;
[[nodiscard]] bool canChangeJoinMuted() const;
[[nodiscard]] bool joinedToTop() const;
private:
enum class ApplySliceSource {
@@ -111,16 +113,32 @@ private:
UnknownLoaded,
UpdateReceived,
};
enum class QueuedType : uint8 {
VersionedParticipant,
Participant,
Call,
};
[[nodiscard]] ApiWrap &api() const;
void discard(const MTPDgroupCallDiscarded &data);
[[nodiscard]] bool inCall() const;
void applyCall(const MTPGroupCall &call, bool force);
void applyParticipantsSlice(
const QVector<MTPGroupCallParticipant> &list,
ApplySliceSource sliceSource);
void requestUnknownParticipants();
void changePeerEmptyCallFlag();
void checkFinishSpeakingByActive();
void applyCallFields(const MTPDgroupCall &data);
void applyEnqueuedUpdate(const MTPUpdate &update);
void setServerParticipantsCount(int count);
void computeParticipantsCount();
void processQueuedUpdates();
void processFullCallUsersChats(const MTPphone_GroupCall &call);
void processFullCallFields(const MTPphone_GroupCall &call);
[[nodiscard]] bool requestParticipantsAfterReload(
const MTPphone_GroupCall &call) const;
void processSavedFullCall();
void finishParticipantsSliceRequest();
const uint64 _id = 0;
const uint64 _accessHash = 0;
@@ -131,11 +149,18 @@ private:
mtpRequestId _reloadRequestId = 0;
rpl::variable<QString> _title;
base::flat_multi_map<
std::pair<int, QueuedType>,
MTPUpdate> _queuedUpdates;
base::Timer _reloadByQueuedUpdatesTimer;
std::optional<MTPphone_GroupCall> _savedFull;
std::vector<Participant> _participants;
base::flat_map<uint32, not_null<PeerData*>> _participantPeerBySsrc;
base::flat_map<not_null<PeerData*>, crl::time> _speakingByActiveFinishes;
base::Timer _speakingByActiveFinishTimer;
QString _nextOffset;
int _serverParticipantsCount = 0;
rpl::variable<int> _fullCount = 0;
rpl::variable<TimeId> _recordStartDate = 0;
@@ -148,7 +173,9 @@ private:
bool _joinMuted = false;
bool _canChangeJoinMuted = true;
bool _allReceived = false;
bool _allParticipantsLoaded = false;
bool _joinedToTop = false;
bool _applyingQueuedUpdates = false;
};

View File

@@ -1172,4 +1172,16 @@ std::optional<int> ResolvePinnedCount(
: std::nullopt;
}
ChatRestrictions ChatBannedRightsFlags(const MTPChatBannedRights &rights) {
return rights.match([](const MTPDchatBannedRights &data) {
return data.vflags().v;
});
}
TimeId ChatBannedRightsUntilDate(const MTPChatBannedRights &rights) {
return rights.match([](const MTPDchatBannedRights &data) {
return data.vuntil_date().v;
});
}
} // namespace Data

View File

@@ -95,6 +95,11 @@ struct UnavailableReason {
}
};
[[nodiscard]] ChatRestrictions ChatBannedRightsFlags(
const MTPChatBannedRights &rights);
[[nodiscard]] TimeId ChatBannedRightsUntilDate(
const MTPChatBannedRights &rights);
} // namespace Data
class PeerClickHandler : public ClickHandler {

View File

@@ -1820,7 +1820,7 @@ Utf8String FormatDateTime(
).toUtf8();
}
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency) {
Utf8String FormatMoneyAmount(int64 amount, const Utf8String &currency) {
return Ui::FillAmountAndCurrency(
amount,
QString::fromUtf8(currency)).toUtf8();

View File

@@ -660,7 +660,7 @@ Utf8String FormatDateTime(
QChar dateSeparator = QChar('.'),
QChar timeSeparator = QChar(':'),
QChar separator = QChar(' '));
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency);
Utf8String FormatMoneyAmount(int64 amount, const Utf8String &currency);
Utf8String FormatFileSize(int64 size);
Utf8String FormatDuration(int64 seconds);

View File

@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_response.h"
#include "base/value_ordering.h"
#include "base/bytes.h"
#include "base/openssl_help.h"
#include <set>
#include <deque>
@@ -182,6 +183,7 @@ struct ApiWrap::FileProcess {
Fn<bool(FileProgress)> progress;
FnMut<void(const QString &relativePath)> done;
uint64 randomId = 0;
Data::FileLocation location;
Data::FileOrigin origin;
int offset = 0;
@@ -192,6 +194,7 @@ struct ApiWrap::FileProcess {
QByteArray bytes;
};
std::deque<Request> requests;
mtpRequestId requestId = 0;
};
struct ApiWrap::FileProgress {
@@ -383,6 +386,7 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
Expects(location.dcId != 0
|| location.data.type() == mtpc_inputTakeoutFileLocation);
Expects(_takeoutId.has_value());
Expects(_fileProcess->requestId == 0);
return std::move(_mtp.request(MTPInvokeWithTakeout<MTPupload_GetFile>(
MTP_long(*_takeoutId),
@@ -392,6 +396,7 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
MTP_int(offset),
MTP_int(kFileChunkSize))
)).fail([=](const MTP::Error &result) {
_fileProcess->requestId = 0;
if (result.type() == qstr("TAKEOUT_FILE_EMPTY")
&& _otherDataProcess != nullptr) {
filePartDone(
@@ -853,6 +858,7 @@ bool ApiWrap::loadUserpicProgress(FileProgress progress) {
< _userpicsProcess->slice->list.size()));
return _userpicsProcess->fileProgress(DownloadProgress{
_fileProcess->randomId,
_fileProcess->relativePath,
_userpicsProcess->fileIndex,
progress.ready,
@@ -1061,6 +1067,17 @@ void ApiWrap::finishExport(FnMut<void()> done) {
)).done(std::move(done)).send();
}
void ApiWrap::skipFile(uint64 randomId) {
if (!_fileProcess || _fileProcess->randomId != randomId) {
return;
}
LOG(("Export Info: File skipped."));
Assert(!_fileProcess->requests.empty());
Assert(_fileProcess->requestId != 0);
_mtp.request(base::take(_fileProcess->requestId)).cancel();
base::take(_fileProcess)->done(QString());
}
void ApiWrap::cancelExportFast() {
if (_takeoutId.has_value()) {
const auto requestId = mainRequest(MTPaccount_FinishTakeoutSession(
@@ -1591,10 +1608,11 @@ bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
&& (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
return _chatProcess->fileProgress(DownloadProgress{
_fileProcess->relativePath,
_chatProcess->fileIndex,
progress.ready,
progress.total });
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _chatProcess->fileIndex,
.ready = progress.ready,
.total = progress.total });
}
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
@@ -1740,6 +1758,8 @@ void ApiWrap::loadFile(
}
loadFilePart();
Ensures(_fileProcess->requestId != 0);
}
auto ApiWrap::prepareFileProcess(
@@ -1758,11 +1778,13 @@ auto ApiWrap::prepareFileProcess(
result->location = file.location;
result->size = file.size;
result->origin = origin;
result->randomId = openssl::RandomValue<uint64>();
return result;
}
void ApiWrap::loadFilePart() {
if (!_fileProcess
|| _fileProcess->requestId
|| _fileProcess->requests.size() >= kFileRequestsCount
|| (_fileProcess->size > 0
&& _fileProcess->offset >= _fileProcess->size)) {
@@ -1771,16 +1793,18 @@ void ApiWrap::loadFilePart() {
const auto offset = _fileProcess->offset;
_fileProcess->requests.push_back({ offset });
fileRequest(
_fileProcess->requestId = fileRequest(
_fileProcess->location,
_fileProcess->offset
).done([=](const MTPupload_File &result) {
_fileProcess->requestId = 0;
filePartDone(offset, result);
}).send();
_fileProcess->offset += kFileChunkSize;
if (_fileProcess->size > 0
&& _fileProcess->requests.size() < kFileRequestsCount) {
// Only one request at a time supported right now.
//const auto runner = _runner;
//crl::on_main([=] {
// QTimer::singleShot(kFileNextRequestDelay, [=] {
@@ -1854,6 +1878,7 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
void ApiWrap::filePartRefreshReference(int offset) {
Expects(_fileProcess != nullptr);
Expects(_fileProcess->requestId == 0);
const auto &origin = _fileProcess->origin;
if (!origin.messageId) {
@@ -1870,26 +1895,33 @@ void ApiWrap::filePartRefreshReference(int offset) {
origin.peer.c_inputPeerChannelFromMessage().vpeer(),
origin.peer.c_inputPeerChannelFromMessage().vmsg_id(),
origin.peer.c_inputPeerChannelFromMessage().vchannel_id());
mainRequest(MTPchannels_GetMessages(
_fileProcess->requestId = mainRequest(MTPchannels_GetMessages(
channel,
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)).fail([=](const MTP::Error &error) {
_fileProcess->requestId = 0;
filePartUnavailable();
return true;
}).done([=](const MTPmessages_Messages &result) {
_fileProcess->requestId = 0;
filePartExtractReference(offset, result);
}).send();
} else {
splitRequest(origin.split, MTPmessages_GetMessages(
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)).fail([=](const MTP::Error &error) {
_fileProcess->requestId = splitRequest(
origin.split,
MTPmessages_GetMessages(
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)
).fail([=](const MTP::Error &error) {
_fileProcess->requestId = 0;
filePartUnavailable();
return true;
}).done([=](const MTPmessages_Messages &result) {
_fileProcess->requestId = 0;
filePartExtractReference(offset, result);
}).send();
}
@@ -1899,6 +1931,7 @@ void ApiWrap::filePartExtractReference(
int offset,
const MTPmessages_Messages &result) {
Expects(_fileProcess != nullptr);
Expects(_fileProcess->requestId == 0);
result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
@@ -1922,10 +1955,11 @@ void ApiWrap::filePartExtractReference(
_fileProcess->location,
message.thumb().file.location);
if (refresh1 || refresh2) {
fileRequest(
_fileProcess->requestId = fileRequest(
_fileProcess->location,
offset
).done([=](const MTPupload_File &result) {
_fileProcess->requestId = 0;
filePartDone(offset, result);
}).send();
return;

View File

@@ -60,6 +60,7 @@ public:
FnMut<void(Data::File&&)> done);
struct DownloadProgress {
uint64 randomId = 0;
QString path;
int itemIndex = 0;
int ready = 0;
@@ -83,6 +84,7 @@ public:
FnMut<void()> done);
void finishExport(FnMut<void()> done);
void skipFile(uint64 randomId);
void cancelExportFast();
~ApiWrap();

View File

@@ -51,12 +51,14 @@ public:
void startExport(
const Settings &settings,
const Environment &environment);
void skipFile(uint64 randomId);
void cancelExportFast();
private:
using Step = ProcessingState::Step;
using DownloadProgress = ApiWrap::DownloadProgress;
[[nodiscard]] bool stopped() const;
void setState(State &&state);
void ioError(const QString &path);
bool ioCatchError(Output::Result result);
@@ -166,8 +168,15 @@ rpl::producer<State> ControllerObject::state() const {
});
}
bool ControllerObject::stopped() const {
return v::is<CancelledState>(_state)
|| v::is<ApiErrorState>(_state)
|| v::is<OutputErrorState>(_state)
|| v::is<FinishedState>(_state);
}
void ControllerObject::setState(State &&state) {
if (v::is<CancelledState>(_state)) {
if (stopped()) {
return;
}
_state = std::move(state);
@@ -245,6 +254,13 @@ void ControllerObject::startExport(
exportNext();
}
void ControllerObject::skipFile(uint64 randomId) {
if (stopped()) {
return;
}
_api.skipFile(randomId);
}
void ControllerObject::fillExportSteps() {
using Type = Settings::Type;
_steps.push_back(Step::Initializing);
@@ -518,6 +534,7 @@ ProcessingState ControllerObject::stateUserpics(
result.entityIndex = _userpicsWritten + progress.itemIndex;
result.entityCount = std::max(_userpicsCount, result.entityIndex);
result.bytesType = ProcessingState::FileType::Photo;
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
@@ -570,6 +587,7 @@ void ControllerObject::fillMessagesState(
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.itemIndex);
result.bytesType = ProcessingState::FileType::File; // TODO
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
@@ -643,6 +661,12 @@ void Controller::startExport(
});
}
void Controller::skipFile(uint64 randomId) {
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.skipFile(randomId);
});
}
void Controller::cancelExportFast() {
LOG(("Export Info: Cancelled export."));

View File

@@ -74,6 +74,7 @@ struct ProcessingState {
int itemIndex = 0;
int itemCount = 0;
uint64 bytesRandomId = 0;
FileType bytesType = FileType::None;
QString bytesName;
int bytesLoaded = 0;
@@ -136,6 +137,7 @@ public:
void startExport(
const Settings &settings,
const Environment &environment);
void skipFile(uint64 randomId);
void cancelExportFast();
rpl::lifetime &lifetime();

View File

@@ -50,7 +50,8 @@ exportErrorLabel: FlatLabel(boxLabel) {
exportProgressDuration: 200;
exportProgressRowHeight: 30px;
exportProgressRowPadding: margins(22px, 10px, 22px, 20px);
exportProgressRowPadding: margins(22px, 10px, 22px, 10px);
exportProgressRowSkip: 10px;
exportProgressLabel: FlatLabel(boxLabel) {
textFg: windowBoldFg;
maxHeight: 20px;

View File

@@ -26,8 +26,9 @@ Content ContentFromState(
const QString &id,
const QString &label,
const QString &info,
float64 progress) {
result.rows.push_back({ id, label, info, progress });
float64 progress,
uint64 randomId = 0) {
result.rows.push_back({ id, label, info, progress, randomId });
};
const auto pushMain = [&](const QString &label) {
const auto info = (state.entityCount > 0)
@@ -56,7 +57,10 @@ Content ContentFromState(
: addPart(state.entityIndex, state.entityCount);
push("main", label, info, doneProgress + addProgress);
};
const auto pushBytes = [&](const QString &id, const QString &label) {
const auto pushBytes = [&](
const QString &id,
const QString &label,
uint64 randomId) {
if (!state.bytesCount) {
return;
}
@@ -64,7 +68,7 @@ Content ContentFromState(
const auto info = Ui::FormatDownloadText(
state.bytesLoaded,
state.bytesCount);
push(id, label, info, progress);
push(id, label, info, progress, randomId);
};
switch (state.step) {
case Step::Initializing:
@@ -80,7 +84,8 @@ Content ContentFromState(
pushMain(tr::lng_export_state_userpics(tr::now));
pushBytes(
"userpic" + QString::number(state.entityIndex),
state.bytesName);
state.bytesName,
state.bytesRandomId);
break;
case Step::Contacts:
pushMain(tr::lng_export_option_contacts(tr::now));
@@ -117,7 +122,8 @@ Content ContentFromState(
+ QString::number(state.entityIndex)
+ '_'
+ QString::number(state.itemIndex)),
state.bytesName);
state.bytesName,
state.bytesRandomId);
break;
default: Unexpected("Step in ContentFromState.");
}

View File

@@ -22,6 +22,7 @@ struct Content {
QString label;
QString info;
float64 progress = 0.;
uint64 randomId = 0;
};
std::vector<Row> rows;

View File

@@ -299,6 +299,11 @@ void PanelController::showProgress() {
ContentFromState(_settings.get(), ProcessingState())
) | rpl::then(progressState()));
progress->skipFileClicks(
) | rpl::start_with_next([=](uint64 randomId) {
_process->skipFile(randomId);
}, progress->lifetime());
progress->cancelClicks(
) | rpl::start_with_next([=] {
stopWithConfirmation();

View File

@@ -18,6 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace View {
namespace {
constexpr auto kShowSkipFileTimeout = 5 * crl::time(1000);
} // namespace
class ProgressWidget::Row : public Ui::RpWidget {
public:
@@ -235,13 +240,26 @@ ProgressWidget::ProgressWidget(
QWidget *parent,
rpl::producer<Content> content)
: RpWidget(parent)
, _body(this) {
, _body(this)
, _fileShowSkipTimer([=] { _skipFile->show(anim::type::normal); }) {
widthValue(
) | rpl::start_with_next([=](int width) {
_body->resizeToWidth(width);
_body->moveToLeft(0, 0);
}, _body->lifetime());
auto skipFileWrap = _body->add(object_ptr<Ui::FixedHeightWidget>(
_body.data(),
st::defaultLinkButton.font->height + st::exportProgressRowSkip));
_skipFile = base::make_unique_q<Ui::FadeWrap<Ui::LinkButton>>(
skipFileWrap,
object_ptr<Ui::LinkButton>(
this,
tr::lng_export_skip_file(tr::now),
st::defaultLinkButton));
_skipFile->hide(anim::type::instant);
_skipFile->moveToLeft(st::exportProgressRowPadding.left(), 0);
_about = _body->add(
object_ptr<Ui::FlatLabel>(
this,
@@ -262,6 +280,11 @@ ProgressWidget::ProgressWidget(
setupBottomButton(_cancel.get());
}
rpl::producer<uint64> ProgressWidget::skipFileClicks() const {
return _skipFile->entity()->clicks(
) | rpl::map([=] { return _fileRandomId; });
}
rpl::producer<> ProgressWidget::cancelClicks() const {
return _cancel
? (_cancel->clicks() | rpl::to_empty)
@@ -294,14 +317,32 @@ void ProgressWidget::updateState(Content &&content) {
if (index < _rows.size()) {
_rows[index]->updateData(std::move(row));
} else {
if (index > 0) {
_body->insert(
index * 2 - 1,
object_ptr<Ui::FixedHeightWidget>(
this,
st::exportProgressRowSkip));
}
_rows.push_back(_body->insert(
index,
index * 2,
object_ptr<Row>(this, std::move(row)),
st::exportProgressRowPadding));
_rows.back()->show();
}
++index;
}
const auto fileRandomId = !content.rows.empty()
? content.rows.back().randomId
: uint64(0);
if (_fileRandomId != fileRandomId) {
_fileShowSkipTimer.cancel();
_skipFile->hide(anim::type::normal);
_fileRandomId = fileRandomId;
if (_fileRandomId) {
_fileShowSkipTimer.callOnce(kShowSkipFileTimeout);
}
}
for (const auto count = _rows.size(); index != count; ++index) {
_rows[index]->updateData(Content::Row());
}
@@ -312,6 +353,8 @@ void ProgressWidget::updateState(Content &&content) {
void ProgressWidget::showDone() {
_cancel = nullptr;
_skipFile->hide(anim::type::instant);
_fileShowSkipTimer.cancel();
_about->setText(tr::lng_export_about_done(tr::now));
_done = base::make_unique_q<Ui::RoundButton>(
this,

View File

@@ -10,11 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "export/view/export_view_content.h"
#include "base/object_ptr.h"
#include "base/timer.h"
namespace Ui {
class VerticalLayout;
class RoundButton;
class FlatLabel;
class LinkButton;
template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Export {
@@ -26,6 +30,7 @@ public:
QWidget *parent,
rpl::producer<Content> content);
rpl::producer<uint64> skipFileClicks() const;
rpl::producer<> cancelClicks() const;
rpl::producer<> doneClicks() const;
@@ -42,11 +47,15 @@ private:
object_ptr<Ui::VerticalLayout> _body;
std::vector<not_null<Row*>> _rows;
base::unique_qptr<Ui::FadeWrap<Ui::LinkButton>> _skipFile;
QPointer<Ui::FlatLabel> _about;
base::unique_qptr<Ui::RoundButton> _cancel;
base::unique_qptr<Ui::RoundButton> _done;
rpl::event_stream<> _doneClicks;
uint64 _fileRandomId = 0;
base::Timer _fileShowSkipTimer;
};
} // namespace View

View File

@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/media/history_view_media.h"
#include "payments/payments_checkout_process.h"
#include "data/data_session.h"
#include "styles/style_chat.h"
@@ -121,7 +122,7 @@ void activateBotCommand(
} break;
case ButtonType::Buy: {
Ui::show(Box<InformBox>(tr::lng_payments_not_supported(tr::now)));
Payments::CheckoutProcess::Start(msg);
} break;
case ButtonType::Url: {
@@ -260,7 +261,7 @@ void showChatsList(not_null<Main::Session*> session) {
if (const auto m = CheckMainWidget(session)) {
m->ui_showPeerHistory(
0,
Window::SectionShow::Way::ClearStack,
::Window::SectionShow::Way::ClearStack,
0);
}
}
@@ -277,7 +278,7 @@ void showPeerHistory(not_null<const PeerData*> peer, MsgId msgId) {
if (const auto m = CheckMainWidget(&peer->session())) {
m->ui_showPeerHistory(
peer->id,
Window::SectionShow::Way::ClearStack,
::Window::SectionShow::Way::ClearStack,
msgId);
}
}

View File

@@ -432,8 +432,13 @@ void InnerWidget::requestAdmins() {
auto filtered = (
list
) | ranges::views::transform([&](const MTPChannelParticipant &p) {
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 canEdit = p.match([](
const MTPDchannelParticipantAdmin &data) {
@@ -441,10 +446,13 @@ void InnerWidget::requestAdmins() {
}, [](const auto &) {
return false;
});
return std::make_pair(userId, canEdit);
return std::make_pair(participantId, canEdit);
}) | ranges::views::transform([&](auto &&pair) {
return std::make_pair(
session().data().userLoaded(pair.first),
(peerIsUser(pair.first)
? session().data().userLoaded(
peerToUser(pair.first))
: nullptr),
pair.second);
}) | ranges::views::filter([&](auto &&pair) {
return (pair.first != nullptr);
@@ -1304,11 +1312,11 @@ void InnerWidget::suggestRestrictUser(not_null<UserData*> user) {
Ui::LayerOption::KeepOther);
};
if (base::contains(_admins, user)) {
editRestrictions(true, MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
editRestrictions(true, ChannelData::EmptyRestrictedRights(user));
} else {
_api.request(MTPchannels_GetParticipant(
_channel->inputChannel,
user->inputUser
user->input
)).done([=](const MTPchannels_ChannelParticipant &result) {
Expects(result.type() == mtpc_channels_channelParticipant);
@@ -1321,15 +1329,11 @@ void InnerWidget::suggestRestrictUser(not_null<UserData*> user) {
} else {
auto hasAdminRights = (type == mtpc_channelParticipantAdmin)
|| (type == mtpc_channelParticipantCreator);
auto bannedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
auto bannedRights = ChannelData::EmptyRestrictedRights(user);
editRestrictions(hasAdminRights, bannedRights);
}
}).fail([=](const MTP::Error &error) {
auto bannedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
auto bannedRights = ChannelData::EmptyRestrictedRights(user);
editRestrictions(false, bannedRights);
}).send();
}
@@ -1352,8 +1356,7 @@ void InnerWidget::restrictUser(
}
void InnerWidget::restrictUserDone(not_null<UserData*> user, const MTPChatBannedRights &rights) {
Expects(rights.type() == mtpc_chatBannedRights);
if (rights.c_chatBannedRights().vflags().v) {
if (Data::ChatBannedRightsFlags(rights)) {
_admins.erase(std::remove(_admins.begin(), _admins.end(), user), _admins.end());
_adminsCanEdit.erase(std::remove(_adminsCanEdit.begin(), _adminsCanEdit.end(), user), _adminsCanEdit.end());
}

View File

@@ -204,14 +204,11 @@ TextWithEntities GenerateAdminChangeText(
QString GenerateBannedChangeText(
const MTPChatBannedRights *newRights,
const MTPChatBannedRights *prevRights) {
Expects(!newRights || newRights->type() == mtpc_chatBannedRights);
Expects(!prevRights || prevRights->type() == mtpc_chatBannedRights);
using Flag = MTPDchatBannedRights::Flag;
using Flags = MTPDchatBannedRights::Flags;
auto newFlags = newRights ? newRights->c_chatBannedRights().vflags().v : Flags(0);
auto prevFlags = prevRights ? prevRights->c_chatBannedRights().vflags().v : Flags(0);
auto newFlags = newRights ? Data::ChatBannedRightsFlags(*newRights) : Flags(0);
auto prevFlags = prevRights ? Data::ChatBannedRightsFlags(*prevRights) : Flags(0);
static auto phraseMap = std::map<Flags, tr::phrase<>>{
{ Flag::f_view_messages, tr::lng_admin_log_banned_view_messages },
{ Flag::f_send_messages, tr::lng_admin_log_banned_send_messages },
@@ -230,19 +227,21 @@ QString GenerateBannedChangeText(
}
TextWithEntities GenerateBannedChangeText(
PeerId participantId,
const TextWithEntities &user,
const MTPChatBannedRights *newRights,
const MTPChatBannedRights *prevRights) {
Expects(!newRights || newRights->type() == mtpc_chatBannedRights);
using Flag = MTPDchatBannedRights::Flag;
using Flags = MTPDchatBannedRights::Flags;
auto newFlags = newRights ? newRights->c_chatBannedRights().vflags().v : Flags(0);
auto newUntil = newRights ? newRights->c_chatBannedRights().vuntil_date().v : TimeId(0);
auto newFlags = newRights ? Data::ChatBannedRightsFlags(*newRights) : Flags(0);
auto newUntil = newRights ? Data::ChatBannedRightsUntilDate(*newRights) : TimeId(0);
auto prevFlags = prevRights ? Data::ChatBannedRightsFlags(*prevRights) : Flags(0);
auto indefinitely = ChannelData::IsRestrictedForever(newUntil);
if (newFlags & Flag::f_view_messages) {
return tr::lng_admin_log_banned(tr::now, lt_user, user, Ui::Text::WithEntities);
} else if (newFlags == 0 && (prevFlags & Flag::f_view_messages) && !peerIsUser(participantId)) {
return tr::lng_admin_log_unbanned(tr::now, lt_user, user, Ui::Text::WithEntities);
}
auto untilText = indefinitely
? tr::lng_admin_log_restricted_forever(tr::now)
@@ -344,21 +343,23 @@ TextWithEntities GenerateInviteLinkChangeText(
return result;
};
auto GenerateUserString(
auto GenerateParticipantString(
not_null<Main::Session*> session,
MTPint userId) {
PeerId participantId) {
// User name in "User name (@username)" format with entities.
auto user = session->data().user(userId.v);
auto name = TextWithEntities { user->name };
auto entityData = QString::number(user->id)
+ '.'
+ QString::number(user->accessHash());
name.entities.push_back({
EntityType::MentionName,
0,
name.text.size(),
entityData });
auto username = user->userName();
auto peer = session->data().peer(participantId);
auto name = TextWithEntities { peer->name };
if (const auto user = peer->asUser()) {
auto entityData = QString::number(user->id)
+ '.'
+ QString::number(user->accessHash());
name.entities.push_back({
EntityType::MentionName,
0,
name.text.size(),
entityData });
}
auto username = peer->userName();
if (username.isEmpty()) {
return name;
}
@@ -381,32 +382,10 @@ auto GenerateParticipantChangeTextInner(
const MTPChannelParticipant &participant,
const MTPChannelParticipant *oldParticipant) {
const auto oldType = oldParticipant ? oldParticipant->type() : 0;
return participant.match([&](const MTPDchannelParticipantCreator &data) {
// No valid string here :(
return tr::lng_admin_log_transferred(
tr::now,
lt_user,
GenerateUserString(&channel->session(), data.vuser_id()),
Ui::Text::WithEntities);
}, [&](const MTPDchannelParticipantAdmin &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
return GenerateAdminChangeText(
channel,
user,
&data.vadmin_rights(),
(oldType == mtpc_channelParticipantAdmin)
? &oldParticipant->c_channelParticipantAdmin().vadmin_rights()
: nullptr);
}, [&](const MTPDchannelParticipantBanned &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
return GenerateBannedChangeText(
user,
&data.vbanned_rights(),
(oldType == mtpc_channelParticipantBanned)
? &oldParticipant->c_channelParticipantBanned().vbanned_rights()
: nullptr);
}, [&](const auto &data) {
auto user = GenerateUserString(&channel->session(), data.vuser_id());
const auto generateOther = [&](PeerId participantId) {
auto user = GenerateParticipantString(
&channel->session(),
participantId);
if (oldType == mtpc_channelParticipantAdmin) {
return GenerateAdminChangeText(
channel,
@@ -415,11 +394,49 @@ auto GenerateParticipantChangeTextInner(
&oldParticipant->c_channelParticipantAdmin().vadmin_rights());
} else if (oldType == mtpc_channelParticipantBanned) {
return GenerateBannedChangeText(
participantId,
user,
nullptr,
&oldParticipant->c_channelParticipantBanned().vbanned_rights());
}
return tr::lng_admin_log_invited(tr::now, lt_user, user, Ui::Text::WithEntities);
};
return participant.match([&](const MTPDchannelParticipantCreator &data) {
// No valid string here :(
return tr::lng_admin_log_transferred(
tr::now,
lt_user,
GenerateParticipantString(
&channel->session(),
peerFromUser(data.vuser_id())),
Ui::Text::WithEntities);
}, [&](const MTPDchannelParticipantAdmin &data) {
const auto user = GenerateParticipantString(
&channel->session(),
peerFromUser(data.vuser_id()));
return GenerateAdminChangeText(
channel,
user,
&data.vadmin_rights(),
(oldType == mtpc_channelParticipantAdmin
? &oldParticipant->c_channelParticipantAdmin().vadmin_rights()
: nullptr));
}, [&](const MTPDchannelParticipantBanned &data) {
const auto participantId = peerFromMTP(data.vpeer());
const auto user = GenerateParticipantString(
&channel->session(),
participantId);
return GenerateBannedChangeText(
participantId,
user,
&data.vbanned_rights(),
(oldType == mtpc_channelParticipantBanned
? &oldParticipant->c_channelParticipantBanned().vbanned_rights()
: nullptr));
}, [&](const MTPDchannelParticipantLeft &data) {
return generateOther(peerFromMTP(data.vpeer()));
}, [&](const auto &data) {
return generateOther(peerFromUser(data.vuser_id()));
});
}

View File

@@ -701,9 +701,11 @@ bool HistoryItem::suggestReport() const {
}
bool HistoryItem::suggestBanReport() const {
auto channel = history()->peer->asChannel();
auto fromUser = from()->asUser();
if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
const auto channel = history()->peer->asChannel();
const auto fromUser = from()->asUser();
if (!channel
|| !fromUser
|| !channel->canRestrictParticipant(fromUser)) {
return false;
}
return !isPost() && !out();

View File

@@ -857,7 +857,12 @@ MsgId HistoryMessage::computeRepliesInboxReadTillFull() const {
? history()->owner().historyLoaded(
peerFromChannel(views->commentsMegagroupId))
: history().get();
return group ? std::max(local, group->inboxReadTillId()) : local;
if (const auto megagroup = group->peer->asChannel()) {
if (megagroup->amIn()) {
return std::max(local, group->inboxReadTillId());
}
}
return local;
}
MsgId HistoryMessage::repliesOutboxReadTill() const {
@@ -891,7 +896,12 @@ MsgId HistoryMessage::computeRepliesOutboxReadTillFull() const {
? history()->owner().historyLoaded(
peerFromChannel(views->commentsMegagroupId))
: history().get();
return group ? std::max(local, group->outboxReadTillId()) : local;
if (const auto megagroup = group->peer->asChannel()) {
if (megagroup->amIn()) {
return std::max(local, group->outboxReadTillId());
}
}
return local;
}
void HistoryMessage::setRepliesMaxId(MsgId maxId) {

View File

@@ -958,7 +958,9 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
UpdateComponents(HistoryServicePayment::Bit());
const auto amount = data.vtotal_amount().v;
const auto currency = qs(data.vcurrency());
Get<HistoryServicePayment>()->amount = Ui::FillAmountAndCurrency(amount, currency);
Get<HistoryServicePayment>()->amount = Ui::FillAmountAndCurrency(
amount,
currency);
} else if (message.vaction().type() == mtpc_messageActionGroupCall) {
const auto &data = message.vaction().c_messageActionGroupCall();
if (data.vduration()) {

View File

@@ -46,12 +46,24 @@ PhoneWidget::PhoneWidget(
, _code(this, st::introCountryCode)
, _phone(this, st::introPhone)
, _checkRequestTimer([=] { checkRequest(); }) {
connect(_phone, SIGNAL(voidBackspace(QKeyEvent*)), _code, SLOT(startErasing(QKeyEvent*)));
connect(_country, SIGNAL(codeChanged(const QString &)), _code, SLOT(codeSelected(const QString &)));
connect(_code, SIGNAL(codeChanged(const QString &)), _country, SLOT(onChooseCode(const QString &)));
connect(_code, SIGNAL(codeChanged(const QString &)), _phone, SLOT(onChooseCode(const QString &)));
_phone->frontBackspaceEvent(
) | rpl::start_with_next([=](not_null<QKeyEvent*> e) {
_code->startErasing(e);
}, _code->lifetime());
connect(_country, &CountryInput::codeChanged, [=](const QString &code) {
_code->codeSelected(code);
});
_code->codeChanged(
) | rpl::start_with_next([=](const QString &code) {
_country->onChooseCode(code);
_phone->chooseCode(code);
}, _code->lifetime());
connect(_country, SIGNAL(codeChanged(const QString &)), _phone, SLOT(onChooseCode(const QString &)));
connect(_code, SIGNAL(addedToNumber(const QString &)), _phone, SLOT(addedToNumber(const QString &)));
_code->addedToNumber(
) | rpl::start_with_next([=](const QString &added) {
_phone->addedToNumber(added);
}, _phone->lifetime());
connect(_phone, &Ui::PhonePartInput::changed, [=] { phoneChanged(); });
connect(_code, &Ui::CountryCodeInput::changed, [=] { phoneChanged(); });
@@ -61,8 +73,8 @@ PhoneWidget::PhoneWidget(
setErrorCentered(true);
setupQrLogin();
if (!_country->onChooseCountry(getData()->country)) {
_country->onChooseCountry(qsl("US"));
if (!_country->chooseCountry(getData()->country)) {
_country->chooseCountry(qsl("US"));
}
_changed = false;
}
@@ -251,7 +263,7 @@ QString PhoneWidget::fullNumber() const {
}
void PhoneWidget::selectCountry(const QString &country) {
_country->onChooseCountry(country);
_country->chooseCountry(country);
}
void PhoneWidget::setInnerFocus() {

View File

@@ -535,7 +535,10 @@ void ValidateSlice(
).arg(from
).arg(index
).arg(till) + strings.join(","));
Unexpected("Bad slice in GroupThumbs.");
if (Logs::DebugEnabled()) {
Unexpected("Bad slice in GroupThumbs.");
}
break;
} else {
keys.emplace(key);
}
@@ -555,9 +558,7 @@ void GroupThumbs::fillItems(
const auto current = (index - from);
const auto old = base::take(_items);
if (Logs::DebugEnabled()) {
ValidateSlice(slice, _context, from, index, till);
}
//ValidateSlice(slice, _context, from, index, till);
markCacheStale();
_items.reserve(till - from);
@@ -586,17 +587,21 @@ void GroupThumbs::animateAliveItems(int current) {
}
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
Expects(_cache.size() >= _items.size());
//Expects(_cache.size() >= _items.size());
_dying.reserve(_cache.size() - _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
animatePreviouslyAlive(old);
markRestAsDying();
}
void GroupThumbs::markRestAsDying() {
Expects(_cache.size() >= _items.size());
//Expects(_cache.size() >= _items.size());
_dying.reserve(_cache.size() - _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
for (const auto &cacheItem : _cache) {
const auto &thumb = cacheItem.second;
const auto state = thumb->state();

View File

@@ -486,7 +486,6 @@ void OverlayWidget::moveEvent(QMoveEvent *e) {
DEBUG_LOG(("Viewer Pos: Moved to %1, %2")
.arg(newPos.x())
.arg(newPos.y()));
moveToScreen();
OverlayParent::moveEvent(e);
}

View File

@@ -101,7 +101,7 @@ void Panel::showBox(
}
void Panel::showToast(const QString &text) {
_widget->showToast(text);
_widget->showToast({ text });
}
Panel::~Panel() = default;

View File

@@ -9,10 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "passport/passport_panel_edit_document.h"
#include "passport/passport_panel_details_row.h"
#include "passport/passport_panel_edit_contact.h"
#include "passport/passport_panel_edit_scans.h"
#include "passport/passport_panel.h"
#include "passport/ui/passport_details_row.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "boxes/passcode_box.h"
@@ -212,7 +212,7 @@ EditDocumentScheme GetDocumentScheme(
result.rows = {
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("first_name"),
tr::lng_passport_first_name(tr::now),
NameValidate,
@@ -221,7 +221,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("middle_name"),
tr::lng_passport_middle_name(tr::now),
NameOrEmptyValidate,
@@ -231,7 +231,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("last_name"),
tr::lng_passport_last_name(tr::now),
NameValidate,
@@ -241,7 +241,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Date,
Ui::PanelDetailsType::Date,
qsl("birth_date"),
tr::lng_passport_birth_date(tr::now),
DateValidate,
@@ -249,7 +249,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Gender,
Ui::PanelDetailsType::Gender,
qsl("gender"),
tr::lng_passport_gender(tr::now),
GenderValidate,
@@ -257,7 +257,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("country_code"),
tr::lng_passport_country(tr::now),
CountryValidate,
@@ -265,7 +265,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("residence_country_code"),
tr::lng_passport_residence_country(tr::now),
CountryValidate,
@@ -273,7 +273,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Scans,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("document_no"),
tr::lng_passport_document_number(tr::now),
DocumentValidate,
@@ -282,7 +282,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Scans,
PanelDetailsType::Date,
Ui::PanelDetailsType::Date,
qsl("expiry_date"),
tr::lng_passport_expiry_date(tr::now),
DateOrEmptyValidate,
@@ -344,7 +344,7 @@ EditDocumentScheme GetDocumentScheme(
auto additional = std::initializer_list<Row>{
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("first_name_native"),
tr::lng_passport_first_name(tr::now),
NativeNameValidate,
@@ -355,7 +355,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("middle_name_native"),
tr::lng_passport_middle_name(tr::now),
NativeNameOrEmptyValidate,
@@ -366,7 +366,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Additional,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("last_name_native"),
tr::lng_passport_last_name(tr::now),
NativeNameValidate,
@@ -411,7 +411,7 @@ EditDocumentScheme GetDocumentScheme(
result.rows = {
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("street_line1"),
tr::lng_passport_street(tr::now),
StreetValidate,
@@ -420,7 +420,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("street_line2"),
tr::lng_passport_street(tr::now),
DontValidate,
@@ -429,7 +429,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("city"),
tr::lng_passport_city(tr::now),
CityValidate,
@@ -438,7 +438,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Text,
Ui::PanelDetailsType::Text,
qsl("state"),
tr::lng_passport_state(tr::now),
DontValidate,
@@ -447,7 +447,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Country,
Ui::PanelDetailsType::Country,
qsl("country_code"),
tr::lng_passport_country(tr::now),
CountryValidate,
@@ -455,7 +455,7 @@ EditDocumentScheme GetDocumentScheme(
},
{
ValueClass::Fields,
PanelDetailsType::Postcode,
Ui::PanelDetailsType::Postcode,
qsl("post_code"),
tr::lng_passport_postcode(tr::now),
PostcodeValidate,

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_contact.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
@@ -278,7 +278,8 @@ void PanelEditContact::setupControls(
wrap.data(),
fieldStyle,
std::move(fieldPlaceholder),
ExtractPhonePrefix(_controller->bot()->session().user()->phone()),
Ui::ExtractPhonePrefix(
_controller->bot()->session().user()->phone()),
data);
} else {
_field = Ui::CreateChild<Ui::MaskedInputField>(

View File

@@ -8,8 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_document.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/passport_panel_edit_scans.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
@@ -19,6 +19,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "data/data_countries.h"
#include "data/data_user.h" // ->bot()->session()
#include "main/main_session.h" // ->session().user()
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "boxes/abstract_box.h"
#include "boxes/confirm_box.h"
@@ -363,7 +366,7 @@ not_null<Ui::RpWidget*> PanelEditDocument::setupContent(
const ValueMap &fields) {
accumulate_max(
maxLabelWidth,
PanelDetailsRow::LabelWidth(row.label));
Ui::PanelDetailsRow::LabelWidth(row.label));
});
if (maxLabelWidth > 0) {
if (error && !error->isEmpty()) {
@@ -513,12 +516,20 @@ void PanelEditDocument::createDetailsRow(
};
const auto current = valueOrEmpty(fields, row.key);
const auto showBox = [controller = _controller](
object_ptr<Ui::BoxContent> box) {
controller->show(std::move(box));
};
const auto isoByPhone = Data::CountryISO2ByPhone(
_controller->bot()->session().user()->phone());
const auto [it, ok] = _details.emplace(
i,
container->add(PanelDetailsRow::Create(
container->add(Ui::PanelDetailsRow::Create(
container,
showBox,
isoByPhone,
row.inputType,
_controller,
row.label,
maxLabelWidth,
current.text,
@@ -537,7 +548,7 @@ void PanelEditDocument::createDetailsRow(
}, it->second->lifetime());
}
not_null<PanelDetailsRow*> PanelEditDocument::findRow(
not_null<Ui::PanelDetailsRow*> PanelEditDocument::findRow(
const QString &key) const {
for (auto i = 0, count = int(_scheme.rows.size()); i != count; ++i) {
const auto &row = _scheme.rows[i];
@@ -636,7 +647,7 @@ bool PanelEditDocument::validate() {
_scroll->scrollToY(_scroll->scrollTop() + scrolldelta);
error = firsttop.y();
}
auto first = QPointer<PanelDetailsRow>();
auto first = QPointer<Ui::PanelDetailsRow>();
for (const auto &[i, field] : ranges::views::reverse(_details)) {
const auto &row = _scheme.rows[i];
if (row.valueClass == Scheme::ValueClass::Additional

View File

@@ -24,15 +24,19 @@ template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Passport::Ui {
using namespace ::Ui;
enum class PanelDetailsType;
class PanelDetailsRow;
} // namespace Passport::Ui
namespace Passport {
class PanelController;
struct ValueMap;
struct ScanInfo;
class EditScans;
class PanelDetailsRow;
enum class FileType;
enum class PanelDetailsType;
struct ScanListData;
struct EditDocumentScheme {
@@ -50,7 +54,7 @@ struct EditDocumentScheme {
using Validator = Fn<std::optional<QString>(const QString &value)>;
using Formatter = Fn<QString(const QString &value)>;
ValueClass valueClass = ValueClass::Fields;
PanelDetailsType inputType = PanelDetailsType();
Ui::PanelDetailsType inputType = Ui::PanelDetailsType();
QString key;
QString label;
Validator error;
@@ -140,7 +144,7 @@ private:
const Scheme::Row &row,
const ValueMap &fields,
int maxLabelWidth);
not_null<PanelDetailsRow*> findRow(const QString &key) const;
not_null<Ui::PanelDetailsRow*> findRow(const QString &key) const;
not_null<PanelController*> _controller;
Scheme _scheme;
@@ -151,7 +155,7 @@ private:
QPointer<EditScans> _editScans;
QPointer<Ui::SlideWrap<Ui::FlatLabel>> _commonError;
std::map<int, QPointer<PanelDetailsRow>> _details;
std::map<int, QPointer<Ui::PanelDetailsRow>> _details;
bool _fieldsChanged = false;
bool _additionalShown = false;

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_edit_scans.h"
#include "passport/passport_panel_controller.h"
#include "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/box_content_divider.h"

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "passport/passport_panel_form.h"
#include "passport/passport_panel_controller.h"
#include "passport/ui/passport_form_row.h"
#include "lang/lang_keys.h"
#include "boxes/abstract_box.h"
#include "core/click_handler_types.h"
@@ -30,145 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Passport {
class PanelForm::Row : public Ui::RippleButton {
public:
explicit Row(QWidget *parent);
void updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated);
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
int countAvailableWidth() const;
int countAvailableWidth(int newWidth) const;
Ui::Text::String _title;
Ui::Text::String _description;
int _titleHeight = 0;
int _descriptionHeight = 0;
bool _ready = false;
bool _error = false;
Ui::Animations::Simple _errorAnimation;
};
PanelForm::Row::Row(QWidget *parent)
: RippleButton(parent, st::passportRowRipple)
, _title(st::boxWideWidth / 2)
, _description(st::boxWideWidth / 2) {
}
void PanelForm::Row::updateContent(
const QString &title,
const QString &description,
bool ready,
bool error,
anim::type animated) {
_title.setText(
st::semiboldTextStyle,
title,
Ui::NameTextOptions());
_description.setText(
st::defaultTextStyle,
description,
TextParseOptions {
TextParseMultiline,
0,
0,
Qt::LayoutDirectionAuto
});
_ready = ready && !error;
if (_error != error) {
_error = error;
if (animated == anim::type::instant) {
_errorAnimation.stop();
} else {
_errorAnimation.start(
[=] { update(); },
_error ? 0. : 1.,
_error ? 1. : 0.,
st::fadeWrapDuration);
}
}
resizeToWidth(width());
update();
}
int PanelForm::Row::resizeGetHeight(int newWidth) {
const auto availableWidth = countAvailableWidth(newWidth);
_titleHeight = _title.countHeight(availableWidth);
_descriptionHeight = _description.countHeight(availableWidth);
const auto result = st::passportRowPadding.top()
+ _titleHeight
+ st::passportRowSkip
+ _descriptionHeight
+ st::passportRowPadding.bottom();
return result;
}
int PanelForm::Row::countAvailableWidth(int newWidth) const {
return newWidth
- st::passportRowPadding.left()
- st::passportRowPadding.right()
- (_ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon).width()
- st::passportRowIconSkip;
}
int PanelForm::Row::countAvailableWidth() const {
return countAvailableWidth(width());
}
void PanelForm::Row::paintEvent(QPaintEvent *e) {
Painter p(this);
paintRipple(p, 0, 0);
const auto left = st::passportRowPadding.left();
const auto availableWidth = countAvailableWidth();
auto top = st::passportRowPadding.top();
const auto error = _errorAnimation.value(_error ? 1. : 0.);
p.setPen(st::passportRowTitleFg);
_title.drawLeft(p, left, top, availableWidth, width());
top += _titleHeight + st::passportRowSkip;
p.setPen(anim::pen(
st::passportRowDescriptionFg,
st::boxTextFgError,
error));
_description.drawLeft(p, left, top, availableWidth, width());
top += _descriptionHeight + st::passportRowPadding.bottom();
const auto &icon = _ready
? st::passportRowReadyIcon
: st::passportRowEmptyIcon;
if (error > 0. && !_ready) {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width(),
anim::color(st::menuIconFgOver, st::boxTextFgError, error));
} else {
icon.paint(
p,
width() - st::passportRowPadding.right() - icon.width(),
(height() - icon.height()) / 2,
width());
}
}
PanelForm::PanelForm(
QWidget *parent,
not_null<PanelController*> controller)

View File

@@ -19,6 +19,11 @@ class FlatLabel;
class UserpicButton;
} // namespace Ui
namespace Passport::Ui {
using namespace ::Ui;
class FormRow;
} // namespace Passport::Ui
namespace Passport {
class PanelController;
@@ -33,7 +38,7 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
class Row;
using Row = Ui::FormRow;
void setupControls();
not_null<Ui::RpWidget*> setupContent();

View File

@@ -5,9 +5,8 @@ the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "passport/passport_panel_details_row.h"
#include "passport/ui/passport_details_row.h"
#include "passport/passport_panel_controller.h"
#include "lang/lang_keys.h"
#include "base/platform/base_platform_info.h"
#include "ui/widgets/input_fields.h"
@@ -15,17 +14,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/countryinput.h"
#include "main/main_session.h"
#include "data/data_user.h"
#include "ui/layers/box_content.h"
#include "ui/boxes/country_select_box.h"
#include "data/data_countries.h"
#include "styles/style_layers.h"
#include "styles/style_passport.h"
namespace Passport {
#include <QtCore/QRegularExpression>
namespace Passport::Ui {
namespace {
class PostcodeInput : public Ui::MaskedInputField {
class PostcodeInput : public MaskedInputField {
public:
PostcodeInput(
QWidget *parent,
@@ -103,7 +103,8 @@ class CountryRow : public PanelDetailsRow {
public:
CountryRow(
QWidget *parent,
not_null<PanelController*> controller,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
const QString &label,
int maxLabelWidth,
const QString &value);
@@ -121,15 +122,16 @@ private:
void toggleError(bool shown);
void errorAnimationCallback();
not_null<PanelController*> _controller;
object_ptr<Ui::LinkButton> _link;
QString _defaultCountry;
Fn<void(object_ptr<BoxContent>)> _showBox;
object_ptr<LinkButton> _link;
rpl::variable<QString> _value;
bool _errorShown = false;
Ui::Animations::Simple _errorAnimation;
Animations::Simple _errorAnimation;
};
class DateInput final : public Ui::MaskedInputField {
class DateInput final : public MaskedInputField {
public:
using MaskedInputField::MaskedInputField;
@@ -191,21 +193,21 @@ private:
int number(const object_ptr<DateInput> &field) const;
object_ptr<DateInput> _day;
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _separator1;
object_ptr<PaddingWrap<FlatLabel>> _separator1;
object_ptr<DateInput> _month;
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>> _separator2;
object_ptr<PaddingWrap<FlatLabel>> _separator2;
object_ptr<DateInput> _year;
rpl::variable<QString> _value;
style::cursor _cursor = style::cur_default;
Ui::Animations::Simple _a_borderShown;
Animations::Simple _a_borderShown;
int _borderAnimationStart = 0;
Ui::Animations::Simple _a_borderOpacity;
Animations::Simple _a_borderOpacity;
bool _borderVisible = false;
Ui::Animations::Simple _a_error;
Animations::Simple _a_error;
bool _error = false;
Ui::Animations::Simple _a_focused;
Animations::Simple _a_focused;
bool _focused = false;
};
@@ -238,18 +240,18 @@ private:
void hideGenderError();
void errorAnimationCallback();
std::unique_ptr<Ui::AbstractCheckView> createRadioView(
Ui::RadioView* &weak) const;
std::unique_ptr<AbstractCheckView> createRadioView(
RadioView* &weak) const;
std::shared_ptr<Ui::RadioenumGroup<Gender>> _group;
Ui::RadioView *_maleRadio = nullptr;
Ui::RadioView *_femaleRadio = nullptr;
object_ptr<Ui::Radioenum<Gender>> _male;
object_ptr<Ui::Radioenum<Gender>> _female;
std::shared_ptr<RadioenumGroup<Gender>> _group;
RadioView *_maleRadio = nullptr;
RadioView *_femaleRadio = nullptr;
object_ptr<Radioenum<Gender>> _male;
object_ptr<Radioenum<Gender>> _female;
rpl::variable<QString> _value;
bool _errorShown = false;
Ui::Animations::Simple _errorAnimation;
Animations::Simple _errorAnimation;
};
@@ -308,12 +310,14 @@ QString CountryString(const QString &code) {
CountryRow::CountryRow(
QWidget *parent,
not_null<PanelController*> controller,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
const QString &label,
int maxLabelWidth,
const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _controller(controller)
, _defaultCountry(defaultCountry)
, _showBox(std::move(showBox))
, _link(this, CountryString(value), st::boxLinkButton)
, _value(value) {
_value.changes(
@@ -380,20 +384,23 @@ void CountryRow::errorAnimationCallback() {
void CountryRow::chooseCountry() {
const auto top = _value.current();
const auto name = Data::CountryNameByISO2(top);
const auto isoByPhone = Data::CountryISO2ByPhone(
_controller->bot()->session().user()->phone());
const auto box = _controller->show(Box<CountrySelectBox>(!name.isEmpty()
const auto country = !name.isEmpty()
? top
: !isoByPhone.isEmpty()
? isoByPhone
: Platform::SystemCountry(),
CountrySelectBox::Type::Countries));
connect(box, &CountrySelectBox::countryChosen, this, [=](QString iso) {
: !_defaultCountry.isEmpty()
? _defaultCountry
: Platform::SystemCountry();
auto box = Box<CountrySelectBox>(
country,
CountrySelectBox::Type::Countries);
const auto raw = box.data();
raw->countryChosen(
) | rpl::start_with_next([=](QString iso) {
_value = iso;
_link->setText(CountryString(iso));
hideCountryError();
box->closeBox();
});
raw->closeBox();
}, lifetime());
_showBox(std::move(box));
}
QDate ValidateDate(const QString &value) {
@@ -528,7 +535,7 @@ DateRow::DateRow(
GetDay(value))
, _separator1(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
QString(" / "),
st::passportDetailsSeparator),
@@ -540,7 +547,7 @@ DateRow::DateRow(
GetMonth(value))
, _separator2(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
QString(" / "),
st::passportDetailsSeparator),
@@ -552,7 +559,7 @@ DateRow::DateRow(
GetYear(value))
, _value(valueCurrent()) {
const auto focused = [=](const object_ptr<DateInput> &field) {
return [this, pointer = Ui::MakeWeak(field.data())]{
return [this, pointer = MakeWeak(field.data())]{
_borderAnimationStart = pointer->borderAnimationStart()
+ pointer->x()
- _day->x();
@@ -565,15 +572,15 @@ DateRow::DateRow(
const auto changed = [=] {
_value = valueCurrent();
};
connect(_day, &Ui::MaskedInputField::focused, focused(_day));
connect(_month, &Ui::MaskedInputField::focused, focused(_month));
connect(_year, &Ui::MaskedInputField::focused, focused(_year));
connect(_day, &Ui::MaskedInputField::blurred, blurred);
connect(_month, &Ui::MaskedInputField::blurred, blurred);
connect(_year, &Ui::MaskedInputField::blurred, blurred);
connect(_day, &Ui::MaskedInputField::changed, changed);
connect(_month, &Ui::MaskedInputField::changed, changed);
connect(_year, &Ui::MaskedInputField::changed, changed);
connect(_day, &MaskedInputField::focused, focused(_day));
connect(_month, &MaskedInputField::focused, focused(_month));
connect(_year, &MaskedInputField::focused, focused(_year));
connect(_day, &MaskedInputField::blurred, blurred);
connect(_month, &MaskedInputField::blurred, blurred);
connect(_year, &MaskedInputField::blurred, blurred);
connect(_day, &MaskedInputField::changed, changed);
connect(_month, &MaskedInputField::changed, changed);
connect(_year, &MaskedInputField::changed, changed);
_day->setMaxValue(31);
_day->putNext() | rpl::start_with_next([=](QChar ch) {
putNext(_month, ch);
@@ -845,8 +852,8 @@ GenderRow::GenderRow(
const QString &value)
: PanelDetailsRow(parent, label, maxLabelWidth)
, _group(StringToGender(value).has_value()
? std::make_shared<Ui::RadioenumGroup<Gender>>(*StringToGender(value))
: std::make_shared<Ui::RadioenumGroup<Gender>>())
? std::make_shared<RadioenumGroup<Gender>>(*StringToGender(value))
: std::make_shared<RadioenumGroup<Gender>>())
, _male(
this,
_group,
@@ -868,9 +875,9 @@ GenderRow::GenderRow(
});
}
std::unique_ptr<Ui::AbstractCheckView> GenderRow::createRadioView(
Ui::RadioView* &weak) const {
auto result = std::make_unique<Ui::RadioView>(st::defaultRadio, false);
std::unique_ptr<AbstractCheckView> GenderRow::createRadioView(
RadioView* &weak) const {
auto result = std::make_unique<RadioView>(st::defaultRadio, false);
weak = result.get();
return result;
}
@@ -959,8 +966,9 @@ PanelDetailsRow::PanelDetailsRow(
object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
QWidget *parent,
Fn<void(object_ptr<BoxContent>)> showBox,
const QString &defaultCountry,
Type type,
not_null<PanelController*> controller,
const QString &label,
int maxLabelWidth,
const QString &value,
@@ -969,7 +977,7 @@ object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
auto result = [&]() -> object_ptr<PanelDetailsRow> {
switch (type) {
case Type::Text:
return object_ptr<AbstractTextRow<Ui::InputField>>(
return object_ptr<AbstractTextRow<InputField>>(
parent,
label,
maxLabelWidth,
@@ -985,7 +993,8 @@ object_ptr<PanelDetailsRow> PanelDetailsRow::Create(
case Type::Country:
return object_ptr<CountryRow>(
parent,
controller,
showBox,
defaultCountry,
label,
maxLabelWidth,
value);
@@ -1062,7 +1071,7 @@ void PanelDetailsRow::showError(std::optional<QString> error) {
if (!_error) {
_error.create(
this,
object_ptr<Ui::FlatLabel>(
object_ptr<FlatLabel>(
this,
*error,
st::passportVerifyErrorLabel));
@@ -1122,4 +1131,4 @@ void PanelDetailsRow::paintEvent(QPaintEvent *e) {
p.drawTextLeft(padding.left(), padding.top(), width(), _label);
}
} // namespace Passport
} // namespace Passport::Ui

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