Compare commits
74 Commits
v2.6.5
...
linux_webv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
672aacd528 | ||
|
|
1a5e807fa9 | ||
|
|
f98f4f0d14 | ||
|
|
78def16ced | ||
|
|
cd7b3419de | ||
|
|
dc4048f1c1 | ||
|
|
bef5320163 | ||
|
|
9a722ea8d4 | ||
|
|
1aefada45d | ||
|
|
f5a8bf0e74 | ||
|
|
738439c334 | ||
|
|
8949c9969b | ||
|
|
cdf6fb1512 | ||
|
|
fb0ea59ff3 | ||
|
|
36f5be60f4 | ||
|
|
46508f7e5e | ||
|
|
28137dfb60 | ||
|
|
e7784620d3 | ||
|
|
462986e9c3 | ||
|
|
c11de2380e | ||
|
|
a432e826a6 | ||
|
|
ea9e85e70f | ||
|
|
d3829c52ec | ||
|
|
9f04570335 | ||
|
|
3c486522a7 | ||
|
|
07cd8c4e83 | ||
|
|
7447c6ea75 | ||
|
|
4d579f873c | ||
|
|
56c8327746 | ||
|
|
0e6d4291a2 | ||
|
|
8ca622d077 | ||
|
|
4d24f28fd0 | ||
|
|
2b3469ef22 | ||
|
|
03a868a6e3 | ||
|
|
12db51fe75 | ||
|
|
ce5579e8f9 | ||
|
|
a16b7fbb83 | ||
|
|
9f6f7f7c9b | ||
|
|
a82d1e863e | ||
|
|
26d97a3636 | ||
|
|
7b8e421996 | ||
|
|
2bc2a0e459 | ||
|
|
7cb4b4f8ab | ||
|
|
b439ecce16 | ||
|
|
a33a4c0589 | ||
|
|
5278e2201f | ||
|
|
3bd6b2268f | ||
|
|
a0a13c3b86 | ||
|
|
0052c7938f | ||
|
|
a14db3e492 | ||
|
|
7979b3b6c8 | ||
|
|
3f25e92afd | ||
|
|
3d1cddaca5 | ||
|
|
eeecc42c25 | ||
|
|
e22ecafc1d | ||
|
|
ba41da7b28 | ||
|
|
9cfbccf9e7 | ||
|
|
2b6f50e114 | ||
|
|
d2f57b72c3 | ||
|
|
85ac983a27 | ||
|
|
ac397e6e19 | ||
|
|
38e15c9bdb | ||
|
|
00d65fa978 | ||
|
|
3fea9cca08 | ||
|
|
b390e0766b | ||
|
|
2f75e6bbe2 | ||
|
|
decbbb9a73 | ||
|
|
b4b80822c8 | ||
|
|
bc82cdc3b3 | ||
|
|
ebc67d25f0 | ||
|
|
348b4d54ba | ||
|
|
6f86ce595b | ||
|
|
8c53a3c19e | ||
|
|
67623072d6 |
4
.github/workflows/linux.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0}
|
||||
shell: scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -65,6 +65,8 @@ jobs:
|
||||
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
|
||||
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
|
||||
- "DESKTOP_APP_DISABLE_GTK_INTEGRATION"
|
||||
- "LIBTGVOIP_DISABLE_ALSA"
|
||||
- "LIBTGVOIP_DISABLE_PULSEAUDIO"
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
|
||||
3
.gitmodules
vendored
@@ -88,3 +88,6 @@
|
||||
[submodule "Telegram/ThirdParty/tgcalls"]
|
||||
path = Telegram/ThirdParty/tgcalls
|
||||
url = https://github.com/TelegramMessenger/tgcalls.git
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
Telegram/Resources/icons/payments/payment_address.png
Normal file
|
After Width: | Height: | Size: 802 B |
BIN
Telegram/Resources/icons/payments/payment_address@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/payments/payment_address@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_card.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
Telegram/Resources/icons/payments/payment_card@2x.png
Normal file
|
After Width: | Height: | Size: 560 B |
BIN
Telegram/Resources/icons/payments/payment_card@3x.png
Normal file
|
After Width: | Height: | Size: 985 B |
BIN
Telegram/Resources/icons/payments/payment_email.png
Normal file
|
After Width: | Height: | Size: 949 B |
BIN
Telegram/Resources/icons/payments/payment_email@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/payments/payment_email@3x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Telegram/Resources/icons/payments/payment_name.png
Normal file
|
After Width: | Height: | Size: 473 B |
BIN
Telegram/Resources/icons/payments/payment_name@2x.png
Normal file
|
After Width: | Height: | Size: 884 B |
BIN
Telegram/Resources/icons/payments/payment_name@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone.png
Normal file
|
After Width: | Height: | Size: 711 B |
BIN
Telegram/Resources/icons/payments/payment_phone@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/payments/payment_phone@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/payments/payment_shipping.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@2x.png
Normal file
|
After Width: | Height: | Size: 1022 B |
BIN
Telegram/Resources/icons/payments/payment_shipping@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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.";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -1573,7 +1573,7 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
|
||||
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
|
||||
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
|
||||
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
|
||||
channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
|
||||
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
|
||||
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
|
||||
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.6.5.0" />
|
||||
Version="2.7.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,5,0
|
||||
PRODUCTVERSION 2,6,5,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.5.0"
|
||||
VALUE "FileVersion", "2.7.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.5.0"
|
||||
VALUE "ProductVersion", "2.7.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,6,5,0
|
||||
PRODUCTVERSION 2,6,5,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.5.0"
|
||||
VALUE "FileVersion", "2.7.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.6.5.0"
|
||||
VALUE "ProductVersion", "2.7.1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -277,13 +277,13 @@ void Updates::checkLastUpdate(bool afterSleep) {
|
||||
|
||||
void Updates::feedUpdateVector(
|
||||
const MTPVector<MTPUpdate> &updates,
|
||||
bool skipMessageIds) {
|
||||
SkipUpdatePolicy policy) {
|
||||
auto list = updates.v;
|
||||
const auto needsSorting = ranges::contains(
|
||||
const auto hasGroupCallParticipantUpdates = ranges::contains(
|
||||
list,
|
||||
mtpc_updateGroupCallParticipants,
|
||||
&MTPUpdate::type);
|
||||
if (needsSorting) {
|
||||
if (hasGroupCallParticipantUpdates) {
|
||||
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
|
||||
if (entry.type() == mtpc_updateGroupCallParticipants) {
|
||||
return 0;
|
||||
@@ -291,9 +291,15 @@ void Updates::feedUpdateVector(
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
if (skipMessageIds && entry.type() == mtpc_updateMessageID) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
&& type == mtpc_updateMessageID)
|
||||
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
|
||||
&& type != mtpc_updateGroupCallParticipants)) {
|
||||
continue;
|
||||
}
|
||||
feedUpdate(entry);
|
||||
@@ -406,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;
|
||||
}
|
||||
|
||||
@@ -567,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) {
|
||||
@@ -824,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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1522,13 +1522,20 @@ void ApiWrap::applyLastParticipantsList(
|
||||
|
||||
auto botStatus = channel->mgInfo->botStatus;
|
||||
const auto emptyAdminRights = MTP_chatAdminRights(MTP_flags(0));
|
||||
const auto emptyRestrictedRights = MTP_chatBannedRights(
|
||||
MTP_flags(0),
|
||||
MTP_int(0));
|
||||
for (const auto &p : list) {
|
||||
const auto userId = p.match([](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
const auto participantId = p.match([](
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const MTPDchannelParticipantLeft &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const auto &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
});
|
||||
if (!participantId) {
|
||||
continue;
|
||||
}
|
||||
const auto participant = _session->data().peer(participantId);
|
||||
const auto user = participant->asUser();
|
||||
const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin)
|
||||
? p.c_channelParticipantAdmin().is_can_edit()
|
||||
: (p.type() == mtpc_channelParticipantCreator)
|
||||
@@ -1541,28 +1548,27 @@ void ApiWrap::applyLastParticipantsList(
|
||||
: emptyAdminRights;
|
||||
const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned)
|
||||
? p.c_channelParticipantBanned().vbanned_rights()
|
||||
: emptyRestrictedRights;
|
||||
if (!userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto user = _session->data().user(userId);
|
||||
: ChannelData::EmptyRestrictedRights(participant);
|
||||
if (p.type() == mtpc_channelParticipantCreator) {
|
||||
Assert(user != nullptr);
|
||||
const auto &creator = p.c_channelParticipantCreator();
|
||||
const auto rank = qs(creator.vrank().value_or_empty());
|
||||
channel->mgInfo->creator = user;
|
||||
channel->mgInfo->creatorRank = rank;
|
||||
if (!channel->mgInfo->admins.empty()) {
|
||||
Data::ChannelAdminChanges(channel).add(userId, rank);
|
||||
Data::ChannelAdminChanges(channel).add(
|
||||
peerToUser(participantId),
|
||||
rank);
|
||||
}
|
||||
}
|
||||
if (!base::contains(channel->mgInfo->lastParticipants, user)) {
|
||||
if (user
|
||||
&& !base::contains(channel->mgInfo->lastParticipants, user)) {
|
||||
channel->mgInfo->lastParticipants.push_back(user);
|
||||
if (adminRights.c_chatAdminRights().vflags().v) {
|
||||
channel->mgInfo->lastAdmins.emplace(
|
||||
user,
|
||||
MegagroupInfo::Admin{ adminRights, adminCanEdit });
|
||||
} else if (restrictedRights.c_chatBannedRights().vflags().v != 0) {
|
||||
} else if (Data::ChatBannedRightsFlags(restrictedRights) != 0) {
|
||||
channel->mgInfo->lastRestricted.emplace(
|
||||
user,
|
||||
MegagroupInfo::Restricted{ restrictedRights });
|
||||
@@ -1607,22 +1613,29 @@ void ApiWrap::applyBotsList(
|
||||
auto botStatus = channel->mgInfo->botStatus;
|
||||
auto keyboardBotFound = !history || !history->lastKeyboardFrom;
|
||||
for (const auto &p : list) {
|
||||
const auto userId = p.match([](const auto &data) {
|
||||
return data.vuser_id().v;
|
||||
const auto participantId = p.match([](
|
||||
const MTPDchannelParticipantBanned &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const MTPDchannelParticipantLeft &data) {
|
||||
return peerFromMTP(data.vpeer());
|
||||
}, [](const auto &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
});
|
||||
if (!userId) {
|
||||
if (!participantId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto user = _session->data().user(userId);
|
||||
if (user->isBot()) {
|
||||
const auto participant = _session->data().peer(participantId);
|
||||
const auto user = participant->asUser();
|
||||
if (user && user->isBot()) {
|
||||
channel->mgInfo->bots.insert(user);
|
||||
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
|
||||
if (!user->botInfo->inited) {
|
||||
needBotsInfos = true;
|
||||
}
|
||||
}
|
||||
if (!keyboardBotFound && user->id == history->lastKeyboardFrom) {
|
||||
if (!keyboardBotFound
|
||||
&& participant->id == history->lastKeyboardFrom) {
|
||||
keyboardBotFound = true;
|
||||
}
|
||||
}
|
||||
@@ -1657,7 +1670,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
_selfParticipantRequests.emplace(channel);
|
||||
request(MTPchannels_GetParticipant(
|
||||
channel->inputChannel,
|
||||
MTP_inputUserSelf()
|
||||
MTP_inputPeerSelf()
|
||||
)).done([=](const MTPchannels_ChannelParticipant &result) {
|
||||
_selfParticipantRequests.erase(channel);
|
||||
result.match([&](const MTPDchannels_channelParticipant &data) {
|
||||
@@ -1698,11 +1711,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
|
||||
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<UserData*> user) {
|
||||
not_null<PeerData*> participant) {
|
||||
Expects(participant->isUser());
|
||||
|
||||
request(MTPmessages_DeleteChatUser(
|
||||
MTP_flags(0),
|
||||
chat->inputChat,
|
||||
user->inputUser
|
||||
participant->asUser()->inputUser
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).send();
|
||||
@@ -1710,21 +1725,21 @@ void ApiWrap::kickParticipant(
|
||||
|
||||
void ApiWrap::kickParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights ¤tRights) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
const auto kick = KickRequest(channel, participant);
|
||||
if (_kickRequests.contains(kick)) return;
|
||||
|
||||
const auto rights = ChannelData::KickedRestrictedRights();
|
||||
const auto rights = ChannelData::KickedRestrictedRights(participant);
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
participant->input,
|
||||
rights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
channel->applyEditBanned(user, currentRights, rights);
|
||||
_kickRequests.remove(KickRequest(channel, participant));
|
||||
channel->applyEditBanned(participant, currentRights, rights);
|
||||
}).fail([this, kick](const MTP::Error &error) {
|
||||
_kickRequests.remove(kick);
|
||||
}).send();
|
||||
@@ -1734,20 +1749,20 @@ void ApiWrap::kickParticipant(
|
||||
|
||||
void ApiWrap::unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto kick = KickRequest(channel, user);
|
||||
not_null<PeerData*> participant) {
|
||||
const auto kick = KickRequest(channel, participant);
|
||||
if (_kickRequests.contains(kick)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto requestId = request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
MTP_chatBannedRights(MTP_flags(0), MTP_int(0))
|
||||
participant->input,
|
||||
ChannelData::EmptyRestrictedRights(participant)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
_kickRequests.remove(KickRequest(channel, user));
|
||||
_kickRequests.remove(KickRequest(channel, participant));
|
||||
if (channel->kickedCount() > 0) {
|
||||
channel->setKickedCount(channel->kickedCount() - 1);
|
||||
} else {
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ¤tRights);
|
||||
void unblockParticipant(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
not_null<PeerData*> participant);
|
||||
void deleteAllFromUser(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> from);
|
||||
@@ -657,7 +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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -71,14 +71,6 @@ void ShowPhoneBannedError(const QString &phone) {
|
||||
[=] { SendToBannedHelp(phone); close(); }));
|
||||
}
|
||||
|
||||
QString ExtractPhonePrefix(const QString &phone) {
|
||||
const auto pattern = phoneNumberParse(phone);
|
||||
if (!pattern.isEmpty()) {
|
||||
return phone.mid(0, pattern[0]);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
SentCodeField::SentCodeField(
|
||||
QWidget *parent,
|
||||
const style::InputField &st,
|
||||
|
||||
@@ -22,7 +22,6 @@ class Session;
|
||||
} // namespace Main
|
||||
|
||||
void ShowPhoneBannedError(const QString &phone);
|
||||
[[nodiscard]] QString ExtractPhonePrefix(const QString &phone);
|
||||
|
||||
class SentCodeField : public Ui::InputField {
|
||||
public:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -222,7 +222,8 @@ MTPChatAdminRights EditAdminBox::defaultRights() const {
|
||||
| Flag::f_post_messages
|
||||
| Flag::f_edit_messages
|
||||
| Flag::f_delete_messages
|
||||
| Flag::f_invite_users);
|
||||
| Flag::f_invite_users
|
||||
| Flag::f_manage_call);
|
||||
return MTP_chatAdminRights(MTP_flags(flags));
|
||||
}
|
||||
|
||||
@@ -611,11 +612,11 @@ void EditRestrictedBox::prepare() {
|
||||
const auto defaultRestrictions = chat
|
||||
? chat->defaultRestrictions()
|
||||
: channel->defaultRestrictions();
|
||||
const auto prepareRights = _oldRights.c_chatBannedRights().vflags().v
|
||||
const auto prepareRights = Data::ChatBannedRightsFlags(_oldRights)
|
||||
? _oldRights
|
||||
: defaultRights();
|
||||
const auto prepareFlags = FixDependentRestrictions(
|
||||
prepareRights.c_chatBannedRights().vflags().v
|
||||
Data::ChatBannedRightsFlags(prepareRights)
|
||||
| defaultRestrictions
|
||||
| ((channel && channel->isPublic())
|
||||
? (Flag::f_change_info | Flag::f_pin_messages)
|
||||
@@ -646,7 +647,7 @@ void EditRestrictedBox::prepare() {
|
||||
disabledMessages);
|
||||
addControl(std::move(checkboxes), QMargins());
|
||||
|
||||
_until = prepareRights.c_chatBannedRights().vuntil_date().v;
|
||||
_until = Data::ChatBannedRightsUntilDate(prepareRights);
|
||||
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
|
||||
addControl(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -766,7 +767,7 @@ void EditRestrictedBox::createUntilVariants() {
|
||||
}
|
||||
};
|
||||
auto addCurrentVariant = [&](TimeId from, TimeId to) {
|
||||
auto oldUntil = _oldRights.c_chatBannedRights().vuntil_date().v;
|
||||
auto oldUntil = Data::ChatBannedRightsUntilDate(_oldRights);
|
||||
if (oldUntil < _until) {
|
||||
addCustomVariant(oldUntil, from, to);
|
||||
}
|
||||
|
||||
@@ -146,18 +146,18 @@ void SaveChannelAdmin(
|
||||
|
||||
void SaveChannelRestriction(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights,
|
||||
Fn<void()> onDone,
|
||||
Fn<void()> onFail) {
|
||||
channel->session().api().request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
user->inputUser,
|
||||
participant->input,
|
||||
newRights
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
channel->applyEditBanned(user, oldRights, newRights);
|
||||
channel->applyEditBanned(participant, oldRights, newRights);
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
@@ -243,7 +243,7 @@ Fn<void(
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
Fn<void(const MTPChatBannedRights &newRights)> onDone,
|
||||
Fn<void()> onFail) {
|
||||
return [=](
|
||||
@@ -253,19 +253,21 @@ Fn<void(
|
||||
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
||||
SaveChannelRestriction(
|
||||
channel,
|
||||
user,
|
||||
participant,
|
||||
oldRights,
|
||||
newRights,
|
||||
done,
|
||||
onFail);
|
||||
};
|
||||
if (const auto chat = peer->asChatNotMigrated()) {
|
||||
const auto flags = newRights.match([](
|
||||
const MTPDchatBannedRights &data) {
|
||||
return data.vflags().v;
|
||||
});
|
||||
if (flags & MTPDchatBannedRights::Flag::f_view_messages) {
|
||||
SaveChatParticipantKick(chat, user, done, onFail);
|
||||
const auto flags = Data::ChatBannedRightsFlags(newRights);
|
||||
if (participant->isUser()
|
||||
&& (flags & MTPDchatBannedRights::Flag::f_view_messages)) {
|
||||
SaveChatParticipantKick(
|
||||
chat,
|
||||
participant->asUser(),
|
||||
done,
|
||||
onFail);
|
||||
} else if (!flags) {
|
||||
done();
|
||||
} else {
|
||||
@@ -313,9 +315,9 @@ ParticipantsAdditionalData::ParticipantsAdditionalData(
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::infoLoaded(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
|| (_infoNotLoaded.find(user) == end(_infoNotLoaded));
|
||||
|| (_infoNotLoaded.find(participant) == end(_infoNotLoaded));
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canEditAdmin(
|
||||
@@ -342,24 +344,27 @@ bool ParticipantsAdditionalData::canAddOrEditAdmin(
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canAddOrEditAdmin.");
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canRestrictUser(
|
||||
not_null<UserData*> user) const {
|
||||
if (!canEditAdmin(user) || user->isSelf()) {
|
||||
bool ParticipantsAdditionalData::canRestrictParticipant(
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (user && (!canEditAdmin(user) || user->isSelf())) {
|
||||
return false;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->canBanMembers();
|
||||
} else if (const auto channel = _peer->asChannel()) {
|
||||
return channel->canBanMembers();
|
||||
}
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canRestrictParticipant.");
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canRemoveUser(
|
||||
not_null<UserData*> user) const {
|
||||
if (canRestrictUser(user)) {
|
||||
bool ParticipantsAdditionalData::canRemoveParticipant(
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (canRestrictParticipant(participant)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return !user->isSelf()
|
||||
return user
|
||||
&& !user->isSelf()
|
||||
&& chat->invitedByMe.contains(user)
|
||||
&& (chat->amCreator() || !_admins.contains(user));
|
||||
}
|
||||
@@ -388,12 +393,12 @@ QString ParticipantsAdditionalData::adminRank(
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::restrictedRights(
|
||||
not_null<UserData*> user) const
|
||||
not_null<PeerData*> participant) const
|
||||
-> std::optional<MTPChatBannedRights> {
|
||||
if (_peer->isChat()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = _restrictedRights.find(user);
|
||||
const auto i = _restrictedRights.find(participant);
|
||||
return (i != end(_restrictedRights))
|
||||
? std::make_optional(i->second)
|
||||
: std::nullopt;
|
||||
@@ -404,16 +409,18 @@ bool ParticipantsAdditionalData::isCreator(not_null<UserData*> user) const {
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::isExternal(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
? !_members.contains(user)
|
||||
: _external.find(user) != end(_external);
|
||||
? (participant->isUser()
|
||||
&& !_members.contains(participant->asUser()))
|
||||
: _external.find(participant) != end(_external);
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::isKicked(not_null<UserData*> user) const {
|
||||
bool ParticipantsAdditionalData::isKicked(
|
||||
not_null<PeerData*> participant) const {
|
||||
return _peer->isChat()
|
||||
? false
|
||||
: _kicked.find(user) != end(_kicked);
|
||||
: _kicked.find(participant) != end(_kicked);
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::adminPromotedBy(
|
||||
@@ -426,29 +433,41 @@ UserData *ParticipantsAdditionalData::adminPromotedBy(
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::restrictedBy(
|
||||
not_null<UserData*> user) const {
|
||||
not_null<PeerData*> participant) const {
|
||||
if (_peer->isChat()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto i = _restrictedBy.find(user);
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
return (i != end(_restrictedBy)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
void ParticipantsAdditionalData::setExternal(not_null<UserData*> user) {
|
||||
_infoNotLoaded.erase(user);
|
||||
_external.emplace(user);
|
||||
void ParticipantsAdditionalData::setExternal(
|
||||
not_null<PeerData*> participant) {
|
||||
if (const auto user = participant->asUser()) {
|
||||
_adminRights.erase(user);
|
||||
_adminCanEdit.erase(user);
|
||||
_adminPromotedBy.erase(user);
|
||||
_admins.erase(user);
|
||||
}
|
||||
_restrictedRights.erase(participant);
|
||||
_kicked.erase(participant);
|
||||
_restrictedBy.erase(participant);
|
||||
_infoNotLoaded.erase(participant);
|
||||
_external.emplace(participant);
|
||||
}
|
||||
|
||||
void ParticipantsAdditionalData::checkForLoaded(not_null<UserData*> user) {
|
||||
void ParticipantsAdditionalData::checkForLoaded(
|
||||
not_null<PeerData*> participant) {
|
||||
const auto contains = [](const auto &map, const auto &value) {
|
||||
return map.find(value) != map.end();
|
||||
};
|
||||
if (_creator != user
|
||||
&& !contains(_adminRights, user)
|
||||
&& !contains(_restrictedRights, user)
|
||||
&& !contains(_external, user)
|
||||
&& !contains(_kicked, user)) {
|
||||
_infoNotLoaded.emplace(user);
|
||||
const auto user = participant->asUser();
|
||||
if (!(user && _creator == user)
|
||||
&& !(user && contains(_adminRights, user))
|
||||
&& !contains(_restrictedRights, participant)
|
||||
&& !contains(_external, participant)
|
||||
&& !contains(_kicked, participant)) {
|
||||
_infoNotLoaded.emplace(participant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,15 +529,15 @@ void ParticipantsAdditionalData::fillFromChannel(
|
||||
}
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
PeerData *ParticipantsAdditionalData::applyParticipant(
|
||||
const MTPChannelParticipant &data) {
|
||||
return applyParticipant(data, _role);
|
||||
}
|
||||
|
||||
UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
PeerData *ParticipantsAdditionalData::applyParticipant(
|
||||
const MTPChannelParticipant &data,
|
||||
Role overrideRole) {
|
||||
const auto logBad = [&]() -> UserData* {
|
||||
const auto logBad = [&]() -> PeerData* {
|
||||
LOG(("API Error: Bad participant type %1 got "
|
||||
"while requesting for participants, role: %2"
|
||||
).arg(data.type()
|
||||
@@ -526,27 +545,28 @@ UserData *ParticipantsAdditionalData::applyParticipant(
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
return data.match([&](const MTPDchannelParticipantCreator &data) {
|
||||
return data.match([&](
|
||||
const MTPDchannelParticipantCreator &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members
|
||||
&& overrideRole != Role::Admins) {
|
||||
return logBad();
|
||||
}
|
||||
return applyCreator(data);
|
||||
}, [&](const MTPDchannelParticipantAdmin &data) {
|
||||
}, [&](const MTPDchannelParticipantAdmin &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members
|
||||
&& overrideRole != Role::Admins) {
|
||||
return logBad();
|
||||
}
|
||||
return applyAdmin(data);
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
}, [&](const MTPDchannelParticipantSelf &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members) {
|
||||
return logBad();
|
||||
}
|
||||
return applyRegular(data.vuser_id());
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
}, [&](const MTPDchannelParticipant &data) -> PeerData* {
|
||||
if (overrideRole != Role::Profile
|
||||
&& overrideRole != Role::Members) {
|
||||
return logBad();
|
||||
@@ -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)(
|
||||
|
||||
@@ -33,7 +33,7 @@ Fn<void(
|
||||
const MTPChatBannedRights &oldRights,
|
||||
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
Fn<void(const MTPChatBannedRights &newRights)> onDone,
|
||||
Fn<void()> onFail);
|
||||
|
||||
@@ -77,29 +77,31 @@ public:
|
||||
|
||||
ParticipantsAdditionalData(not_null<PeerData*> peer, Role role);
|
||||
|
||||
UserData *applyParticipant(const MTPChannelParticipant &data);
|
||||
UserData *applyParticipant(
|
||||
PeerData *applyParticipant(const MTPChannelParticipant &data);
|
||||
PeerData *applyParticipant(
|
||||
const MTPChannelParticipant &data,
|
||||
Role overrideRole);
|
||||
void setExternal(not_null<UserData*> user);
|
||||
void checkForLoaded(not_null<UserData*> user);
|
||||
void setExternal(not_null<PeerData*> participant);
|
||||
void checkForLoaded(not_null<PeerData*> participant);
|
||||
void fillFromPeer();
|
||||
|
||||
[[nodiscard]] bool infoLoaded(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool infoLoaded(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictParticipant(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool canRemoveParticipant(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<MTPChatBannedRights> restrictedRights(
|
||||
not_null<UserData*> user) const;
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isKicked(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
|
||||
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -107,7 +109,7 @@ private:
|
||||
UserData *applyCreator(const MTPDchannelParticipantCreator &data);
|
||||
UserData *applyAdmin(const MTPDchannelParticipantAdmin &data);
|
||||
UserData *applyRegular(MTPint userId);
|
||||
UserData *applyBanned(const MTPDchannelParticipantBanned &data);
|
||||
PeerData *applyBanned(const MTPDchannelParticipantBanned &data);
|
||||
void fillFromChat(not_null<ChatData*> chat);
|
||||
void fillFromChannel(not_null<ChannelData*> channel);
|
||||
|
||||
@@ -124,11 +126,11 @@ private:
|
||||
base::flat_map<not_null<UserData*>, QString> _adminRanks;
|
||||
base::flat_set<not_null<UserData*>> _adminCanEdit;
|
||||
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
|
||||
std::map<not_null<UserData*>, MTPChatBannedRights> _restrictedRights;
|
||||
std::set<not_null<UserData*>> _kicked;
|
||||
std::map<not_null<UserData*>, not_null<UserData*>> _restrictedBy;
|
||||
std::set<not_null<UserData*>> _external;
|
||||
std::set<not_null<UserData*>> _infoNotLoaded;
|
||||
std::map<not_null<PeerData*>, MTPChatBannedRights> _restrictedRights;
|
||||
std::set<not_null<PeerData*>> _kicked;
|
||||
std::map<not_null<PeerData*>, not_null<UserData*>> _restrictedBy;
|
||||
std::set<not_null<PeerData*>> _external;
|
||||
std::set<not_null<PeerData*>> _infoNotLoaded;
|
||||
|
||||
};
|
||||
|
||||
@@ -181,7 +183,7 @@ protected:
|
||||
Role role);
|
||||
|
||||
virtual std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
private:
|
||||
using Row = Info::Profile::MemberListRow;
|
||||
@@ -223,23 +225,25 @@ private:
|
||||
const QString &rank);
|
||||
void showRestricted(not_null<UserData*> user);
|
||||
void editRestrictedDone(
|
||||
not_null<UserData*> user,
|
||||
not_null<PeerData*> participant,
|
||||
const MTPChatBannedRights &rights);
|
||||
void removeKicked(not_null<PeerListRow*> row, not_null<UserData*> user);
|
||||
void removeKickedWithRow(not_null<UserData*> user);
|
||||
void removeKicked(not_null<UserData*> user);
|
||||
void kickMember(not_null<UserData*> user);
|
||||
void kickMemberSure(not_null<UserData*> user);
|
||||
void unkickMember(not_null<UserData*> user);
|
||||
void removeKicked(
|
||||
not_null<PeerListRow*> row,
|
||||
not_null<PeerData*> participant);
|
||||
void removeKickedWithRow(not_null<PeerData*> participant);
|
||||
void removeKicked(not_null<PeerData*> participant);
|
||||
void kickParticipant(not_null<PeerData*> participant);
|
||||
void kickParticipantSure(not_null<PeerData*> participant);
|
||||
void unkickParticipant(not_null<UserData*> user);
|
||||
void removeAdmin(not_null<UserData*> user);
|
||||
void removeAdminSure(not_null<UserData*> user);
|
||||
bool appendRow(not_null<UserData*> user);
|
||||
bool prependRow(not_null<UserData*> user);
|
||||
bool removeRow(not_null<UserData*> user);
|
||||
bool appendRow(not_null<PeerData*> participant);
|
||||
bool prependRow(not_null<PeerData*> participant);
|
||||
bool removeRow(not_null<PeerData*> participant);
|
||||
void refreshCustomStatus(not_null<PeerListRow*> row) const;
|
||||
bool feedMegagroupLastParticipants();
|
||||
Type computeType(not_null<UserData*> user) const;
|
||||
void recomputeTypeFor(not_null<UserData*> user);
|
||||
Type computeType(not_null<PeerData*> participant) const;
|
||||
void recomputeTypeFor(not_null<PeerData*> participant);
|
||||
|
||||
void subscribeToMigration();
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/single_choice_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
@@ -20,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
|
||||
@@ -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) }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -257,31 +288,27 @@ void ChooseJoinAsProcess::start(
|
||||
info.possibleJoinAs = std::move(list);
|
||||
|
||||
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
|
||||
&& (info.possibleJoinAs.front() == self)
|
||||
&& (!peer->isChannel()
|
||||
|| !peer->asChannel()->amAnonymous()
|
||||
|| (peer->isBroadcast() && !peer->canWrite()));
|
||||
&& (info.possibleJoinAs.front() == self);
|
||||
|
||||
// We already joined this voice chat, just rejoin with the same.
|
||||
const auto byAlreadyUsed = selectedId
|
||||
&& (info.joinAs->id == selectedId);
|
||||
&& (info.joinAs->id == selectedId)
|
||||
&& (peer->groupCall() != nullptr);
|
||||
|
||||
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
|
||||
if (context != Context::JoinWithConfirm) {
|
||||
const auto confirmation = CreateOrJoinConfirmation(
|
||||
peer,
|
||||
context,
|
||||
byAlreadyUsed);
|
||||
if (confirmation.text.isEmpty()) {
|
||||
finish(info);
|
||||
return;
|
||||
}
|
||||
const auto real = peer->groupCall();
|
||||
const auto name = (real && !real->title().isEmpty())
|
||||
? real->title()
|
||||
: peer->name;
|
||||
auto box = Box<::ConfirmBox>(
|
||||
tr::lng_group_call_join_confirm(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities),
|
||||
tr::lng_group_call_join(tr::now),
|
||||
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([=] {
|
||||
|
||||
@@ -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"
|
||||
@@ -387,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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -467,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
|
||||
@@ -713,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
|
||||
@@ -849,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;
|
||||
@@ -889,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()) {
|
||||
@@ -1087,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();
|
||||
}
|
||||
@@ -1095,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({
|
||||
|
||||
@@ -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();
|
||||
@@ -277,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;
|
||||
@@ -305,6 +310,7 @@ private:
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint32 _mySsrc = 0;
|
||||
base::flat_set<uint32> _mySsrcs;
|
||||
mtpRequestId _createRequestId = 0;
|
||||
mtpRequestId _updateMuteRequestId = 0;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -375,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)
|
||||
@@ -435,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.,
|
||||
@@ -535,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,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(
|
||||
@@ -685,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
|
||||
@@ -699,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();
|
||||
}
|
||||
|
||||
@@ -738,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,
|
||||
@@ -821,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)),
|
||||
@@ -1573,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);
|
||||
});
|
||||
|
||||
@@ -1597,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(
|
||||
@@ -1606,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()) {
|
||||
@@ -1616,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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
@@ -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,
|
||||
@@ -609,13 +610,13 @@ void Panel::setupJoinAsChangedToasts() {
|
||||
return (state == State::Joined);
|
||||
}) | rpl::take(1);
|
||||
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
|
||||
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = widget(),
|
||||
.text = tr::lng_group_call_join_as_changed(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Ui::Text::Bold(_call->joinAs()->name),
|
||||
Ui::Text::WithEntities),
|
||||
.parentOverride = widget(),
|
||||
});
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
@@ -629,13 +630,13 @@ void Panel::setupTitleChangedToasts() {
|
||||
? _peer->name
|
||||
: _peer->groupCall()->title();
|
||||
}) | rpl::start_with_next([=](const QString &title) {
|
||||
Ui::ShowMultilineToast(Ui::MultilineToastArgs{
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = widget(),
|
||||
.text = tr::lng_group_call_title_changed(
|
||||
tr::now,
|
||||
lt_title,
|
||||
Ui::Text::Bold(title),
|
||||
Ui::Text::WithEntities),
|
||||
.parentOverride = widget(),
|
||||
});
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -427,28 +427,23 @@ void Instance::handleGroupCallUpdate(
|
||||
} else {
|
||||
applyGroupCallUpdateChecked(session, update);
|
||||
}
|
||||
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::applyGroupCallUpdateChecked(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPUpdate &update) {
|
||||
if (!_currentGroupCall
|
||||
|| (&_currentGroupCall->peer()->session() != session)) {
|
||||
return;
|
||||
if (_currentGroupCall
|
||||
&& (&_currentGroupCall->peer()->session() == session)) {
|
||||
_currentGroupCall->handleUpdate(update);
|
||||
}
|
||||
|
||||
update.match([&](const MTPDupdateGroupCall &data) {
|
||||
_currentGroupCall->handleUpdate(data.vcall());
|
||||
}, [&](const MTPDupdateGroupCallParticipants &data) {
|
||||
const auto callId = data.vcall().match([](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
if (_currentGroupCall->id() == callId) {
|
||||
_currentGroupCall->handleUpdate(data);
|
||||
}
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in Instance::applyGroupCallUpdateChecked.");
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::handleSignalingData(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -137,6 +137,16 @@ std::map<int, const char*> BetaLogs() {
|
||||
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"
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ void ComputeExternalUpdater() {
|
||||
while (!fileStream.atEnd()) {
|
||||
const auto path = fileStream.readLine();
|
||||
|
||||
if (path == (cWorkingDir() + cExeName())) {
|
||||
if (path == (cExeDir() + cExeName())) {
|
||||
SetUpdaterDisabledAtStartup();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = 2006005;
|
||||
constexpr auto AppVersionStr = "2.6.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 2007001;
|
||||
constexpr auto AppVersionStr = "2.7.1";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ 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
|
||||
|
||||
GroupCall::GroupCall(
|
||||
@@ -50,6 +56,10 @@ uint64 GroupCall::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
bool GroupCall::loaded() const {
|
||||
return _version > 0;
|
||||
}
|
||||
|
||||
not_null<PeerData*> GroupCall::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
@@ -71,18 +81,24 @@ auto GroupCall::participants() const
|
||||
}
|
||||
|
||||
void GroupCall::requestParticipants() {
|
||||
if (_participantsRequestId || _reloadRequestId) {
|
||||
return;
|
||||
} else if (_allParticipantsLoaded) {
|
||||
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());
|
||||
@@ -94,20 +110,31 @@ void GroupCall::requestParticipants() {
|
||||
if (data.vparticipants().v.isEmpty()) {
|
||||
_allParticipantsLoaded = true;
|
||||
}
|
||||
computeParticipantsCount();
|
||||
_participantsSliceAdded.fire({});
|
||||
_participantsRequestId = 0;
|
||||
processQueuedUpdates();
|
||||
finishParticipantsSliceRequest();
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_participantsRequestId = 0;
|
||||
processSavedFullCall();
|
||||
setServerParticipantsCount(_participants.size());
|
||||
_allParticipantsLoaded = true;
|
||||
computeParticipantsCount();
|
||||
_participantsRequestId = 0;
|
||||
processQueuedUpdates();
|
||||
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();
|
||||
@@ -175,13 +202,23 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
|
||||
update.match([&](const MTPDupdateGroupCall &updateData) {
|
||||
updateData.vcall().match([&](const MTPDgroupCall &data) {
|
||||
const auto version = data.vversion().v;
|
||||
if (!_version || _version == version) {
|
||||
applyUpdate(update);
|
||||
} else if (_version < version) {
|
||||
_queuedUpdates.emplace(std::pair{ version, false }, update);
|
||||
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) {
|
||||
applyUpdate(update);
|
||||
discard(data);
|
||||
});
|
||||
}, [&](const MTPDupdateGroupCallParticipants &updateData) {
|
||||
const auto version = updateData.vversion().v;
|
||||
@@ -195,10 +232,22 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
|
||||
true,
|
||||
proj);
|
||||
const auto required = increment ? (version - 1) : version;
|
||||
if (_version == required) {
|
||||
applyUpdate(update);
|
||||
} else if (_version < required) {
|
||||
_queuedUpdates.emplace(std::pair{ version, increment }, update);
|
||||
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.");
|
||||
@@ -206,7 +255,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
|
||||
processQueuedUpdates();
|
||||
}
|
||||
|
||||
void GroupCall::discard() {
|
||||
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
|
||||
const auto id = _id;
|
||||
const auto peer = _peer;
|
||||
crl::on_main(&peer->session(), [=] {
|
||||
@@ -218,22 +267,28 @@ void GroupCall::discard() {
|
||||
}
|
||||
}
|
||||
});
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&peer->session(),
|
||||
MTP_updateGroupCall(
|
||||
MTP_int(peer->bareId()),
|
||||
MTP_groupCallDiscarded(
|
||||
data.vid(),
|
||||
data.vaccess_hash(),
|
||||
data.vduration())));
|
||||
}
|
||||
|
||||
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
|
||||
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) {
|
||||
if (data.vversion().v == _version
|
||||
&& data.vparticipants_count().v == _serverParticipantsCount
|
||||
&& (_serverParticipantsCount >= _participants.size())
|
||||
&& (!_allParticipantsLoaded
|
||||
|| _serverParticipantsCount == _participants.size())) {
|
||||
return;
|
||||
}
|
||||
_participants.clear();
|
||||
_speakingByActiveFinishes.clear();
|
||||
_participantPeerBySsrc.clear();
|
||||
@@ -245,16 +300,23 @@ void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
|
||||
_nextOffset = nextOffset;
|
||||
|
||||
applyCallFields(data);
|
||||
|
||||
_participantsSliceAdded.fire({});
|
||||
}, [&](const MTPDgroupCallDiscarded &data) {
|
||||
discard();
|
||||
discard(data);
|
||||
});
|
||||
processQueuedUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
void GroupCall::processFullCall(const MTPphone_GroupCall &call) {
|
||||
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."));
|
||||
@@ -269,8 +331,6 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) {
|
||||
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
||||
_allParticipantsLoaded
|
||||
= (_serverParticipantsCount == _participants.size());
|
||||
computeParticipantsCount();
|
||||
processQueuedUpdates();
|
||||
}
|
||||
|
||||
void GroupCall::applyLocalUpdate(
|
||||
@@ -280,14 +340,24 @@ void GroupCall::applyLocalUpdate(
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}
|
||||
|
||||
void GroupCall::applyUpdate(const MTPUpdate &update) {
|
||||
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();
|
||||
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: "
|
||||
@@ -298,7 +368,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
|
||||
data.vparticipants().v,
|
||||
ApplySliceSource::UpdateReceived);
|
||||
}, [](const auto &) {
|
||||
Unexpected("Type in GroupCall::processQueuedUpdates.");
|
||||
Unexpected("Type in GroupCall::applyEnqueuedUpdate.");
|
||||
});
|
||||
Core::App().calls().applyGroupCallUpdateChecked(
|
||||
&_peer->session(),
|
||||
@@ -306,7 +376,7 @@ void GroupCall::applyUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
|
||||
void GroupCall::processQueuedUpdates() {
|
||||
if (!_version) {
|
||||
if (!_version || _applyingQueuedUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,26 +384,22 @@ void GroupCall::processQueuedUpdates() {
|
||||
while (!_queuedUpdates.empty()) {
|
||||
const auto &entry = _queuedUpdates.front();
|
||||
const auto version = entry.first.first;
|
||||
const auto versionIncremented = entry.first.second;
|
||||
const auto type = entry.first.second;
|
||||
const auto incremented = (type == QueuedType::VersionedParticipant);
|
||||
if ((version < _version)
|
||||
|| (version == _version && versionIncremented)) {
|
||||
|| (version == _version && incremented)) {
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
} else if (version == _version
|
||||
|| (version == _version + 1 && versionIncremented)) {
|
||||
|| (version == _version + 1 && incremented)) {
|
||||
const auto update = entry.second;
|
||||
_queuedUpdates.erase(_queuedUpdates.begin());
|
||||
applyUpdate(update);
|
||||
applyEnqueuedUpdate(update);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_queuedUpdates.empty()) {
|
||||
const auto server = _serverParticipantsCount;
|
||||
const auto local = int(_participants.size());
|
||||
if (server < local
|
||||
|| (_allParticipantsLoaded && server > local)) {
|
||||
reload();
|
||||
}
|
||||
_reloadByQueuedUpdatesTimer.cancel();
|
||||
} else if (_queuedUpdates.size() != size
|
||||
|| !_reloadByQueuedUpdatesTimer.isActive()) {
|
||||
_reloadByQueuedUpdatesTimer.callOnce(kWaitForUpdatesTimeout);
|
||||
@@ -347,26 +413,54 @@ void GroupCall::computeParticipantsCount() {
|
||||
}
|
||||
|
||||
void GroupCall::reload() {
|
||||
if (_reloadRequestId) {
|
||||
if (_reloadRequestId || _applyingQueuedUpdates) {
|
||||
return;
|
||||
} else if (_participantsRequestId) {
|
||||
api().request(_participantsRequestId).cancel();
|
||||
_participantsRequestId = 0;
|
||||
}
|
||||
|
||||
_queuedUpdates.clear();
|
||||
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) {
|
||||
@@ -565,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);) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -112,9 +113,14 @@ private:
|
||||
UnknownLoaded,
|
||||
UpdateReceived,
|
||||
};
|
||||
enum class QueuedType : uint8 {
|
||||
VersionedParticipant,
|
||||
Participant,
|
||||
Call,
|
||||
};
|
||||
[[nodiscard]] ApiWrap &api() const;
|
||||
|
||||
void discard();
|
||||
void discard(const MTPDgroupCallDiscarded &data);
|
||||
[[nodiscard]] bool inCall() const;
|
||||
void applyParticipantsSlice(
|
||||
const QVector<MTPGroupCallParticipant> &list,
|
||||
@@ -123,10 +129,16 @@ private:
|
||||
void changePeerEmptyCallFlag();
|
||||
void checkFinishSpeakingByActive();
|
||||
void applyCallFields(const MTPDgroupCall &data);
|
||||
void applyUpdate(const MTPUpdate &update);
|
||||
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;
|
||||
@@ -137,8 +149,11 @@ private:
|
||||
mtpRequestId _reloadRequestId = 0;
|
||||
rpl::variable<QString> _title;
|
||||
|
||||
base::flat_map<std::pair<int,bool>, MTPUpdate> _queuedUpdates;
|
||||
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;
|
||||
@@ -160,6 +175,7 @@ private:
|
||||
bool _canChangeJoinMuted = true;
|
||||
bool _allParticipantsLoaded = false;
|
||||
bool _joinedToTop = false;
|
||||
bool _applyingQueuedUpdates = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -95,6 +95,11 @@ struct UnavailableReason {
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] ChatRestrictions ChatBannedRightsFlags(
|
||||
const MTPChatBannedRights &rights);
|
||||
[[nodiscard]] TimeId ChatBannedRightsUntilDate(
|
||||
const MTPChatBannedRights &rights);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
class PeerClickHandler : public ClickHandler {
|
||||
|
||||
@@ -1820,7 +1820,7 @@ Utf8String FormatDateTime(
|
||||
).toUtf8();
|
||||
}
|
||||
|
||||
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String ¤cy) {
|
||||
Utf8String FormatMoneyAmount(int64 amount, const Utf8String ¤cy) {
|
||||
return Ui::FillAmountAndCurrency(
|
||||
amount,
|
||||
QString::fromUtf8(currency)).toUtf8();
|
||||
|
||||
@@ -660,7 +660,7 @@ Utf8String FormatDateTime(
|
||||
QChar dateSeparator = QChar('.'),
|
||||
QChar timeSeparator = QChar(':'),
|
||||
QChar separator = QChar(' '));
|
||||
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String ¤cy);
|
||||
Utf8String FormatMoneyAmount(int64 amount, const Utf8String ¤cy);
|
||||
Utf8String FormatFileSize(int64 size);
|
||||
Utf8String FormatDuration(int64 seconds);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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."));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ struct Content {
|
||||
QString label;
|
||||
QString info;
|
||||
float64 progress = 0.;
|
||||
uint64 randomId = 0;
|
||||
};
|
||||
|
||||
std::vector<Row> rows;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ void Panel::showBox(
|
||||
}
|
||||
|
||||
void Panel::showToast(const QString &text) {
|
||||
_widget->showToast(text);
|
||||
_widget->showToast({ text });
|
||||
}
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -11,18 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class InputField;
|
||||
class FlatLabel;
|
||||
template <typename Widget>
|
||||
class SlideWrap;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Passport {
|
||||
namespace Passport::Ui {
|
||||
|
||||
class PanelController;
|
||||
using namespace ::Ui;
|
||||
|
||||
enum class PanelDetailsType {
|
||||
Text,
|
||||
@@ -32,7 +32,7 @@ enum class PanelDetailsType {
|
||||
Gender,
|
||||
};
|
||||
|
||||
class PanelDetailsRow : public Ui::RpWidget {
|
||||
class PanelDetailsRow : public RpWidget {
|
||||
public:
|
||||
using Type = PanelDetailsType;
|
||||
|
||||
@@ -43,8 +43,9 @@ public:
|
||||
|
||||
static object_ptr<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,
|
||||
@@ -74,11 +75,11 @@ private:
|
||||
|
||||
QString _label;
|
||||
int _maxLabelWidth = 0;
|
||||
object_ptr<Ui::SlideWrap<Ui::FlatLabel>> _error = { nullptr };
|
||||
object_ptr<SlideWrap<FlatLabel>> _error = { nullptr };
|
||||
bool _errorShown = false;
|
||||
bool _errorHideSubscription = false;
|
||||
Ui::Animations::Simple _errorAnimation;
|
||||
Animations::Simple _errorAnimation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Passport
|
||||
} // namespace Passport::Ui
|
||||
125
Telegram/SourceFiles/passport/ui/passport_form_row.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "passport/ui/passport_form_row.h"
|
||||
|
||||
#include "ui/text/text_options.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Passport::Ui {
|
||||
|
||||
FormRow::FormRow(QWidget *parent)
|
||||
: RippleButton(parent, st::passportRowRipple)
|
||||
, _title(st::boxWideWidth / 2)
|
||||
, _description(st::boxWideWidth / 2) {
|
||||
}
|
||||
|
||||
void FormRow::updateContent(
|
||||
const QString &title,
|
||||
const QString &description,
|
||||
bool ready,
|
||||
bool error,
|
||||
anim::type animated) {
|
||||
_title.setText(
|
||||
st::semiboldTextStyle,
|
||||
title,
|
||||
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 FormRow::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 FormRow::countAvailableWidth(int newWidth) const {
|
||||
return newWidth
|
||||
- st::passportRowPadding.left()
|
||||
- st::passportRowPadding.right()
|
||||
- (_ready
|
||||
? st::passportRowReadyIcon
|
||||
: st::passportRowEmptyIcon).width()
|
||||
- st::passportRowIconSkip;
|
||||
}
|
||||
|
||||
int FormRow::countAvailableWidth() const {
|
||||
return countAvailableWidth(width());
|
||||
}
|
||||
|
||||
void FormRow::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());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Passport::Ui
|
||||
48
Telegram/SourceFiles/passport/ui/passport_form_row.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
||||
namespace Passport::Ui {
|
||||
|
||||
using namespace ::Ui;
|
||||
|
||||
class FormRow : public RippleButton {
|
||||
public:
|
||||
explicit FormRow(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;
|
||||
|
||||
Text::String _title;
|
||||
Text::String _description;
|
||||
int _titleHeight = 0;
|
||||
int _descriptionHeight = 0;
|
||||
bool _ready = false;
|
||||
bool _error = false;
|
||||
Animations::Simple _errorAnimation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Passport::Ui
|
||||
462
Telegram/SourceFiles/payments/payments_checkout_process.cpp
Normal file
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "payments/payments_checkout_process.h"
|
||||
|
||||
#include "payments/payments_form.h"
|
||||
#include "payments/ui/payments_panel.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "storage/storage_domain.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "data/data_user.h" // UserData::isBot.
|
||||
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
|
||||
#include "core/file_utilities.h" // File::OpenUrl.
|
||||
#include "apiwrap.h"
|
||||
|
||||
// #TODO payments errors
|
||||
#include "mainwindow.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
namespace Payments {
|
||||
namespace {
|
||||
|
||||
struct SessionProcesses {
|
||||
base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> map;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
base::flat_map<not_null<Main::Session*>, SessionProcesses> Processes;
|
||||
|
||||
[[nodiscard]] SessionProcesses &LookupSessionProcesses(
|
||||
not_null<const HistoryItem*> item) {
|
||||
const auto session = &item->history()->session();
|
||||
const auto i = Processes.find(session);
|
||||
if (i != end(Processes)) {
|
||||
return i->second;
|
||||
}
|
||||
const auto j = Processes.emplace(session).first;
|
||||
auto &result = j->second;
|
||||
session->account().sessionChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
Processes.erase(session);
|
||||
}, result.lifetime);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CheckoutProcess::Start(not_null<const HistoryItem*> item) {
|
||||
auto &processes = LookupSessionProcesses(item);
|
||||
const auto session = &item->history()->session();
|
||||
const auto id = item->fullId();
|
||||
const auto i = processes.map.find(id);
|
||||
if (i != end(processes.map)) {
|
||||
i->second->requestActivate();
|
||||
return;
|
||||
}
|
||||
const auto j = processes.map.emplace(
|
||||
id,
|
||||
std::make_unique<CheckoutProcess>(session, id, PrivateTag{})).first;
|
||||
j->second->requestActivate();
|
||||
}
|
||||
|
||||
CheckoutProcess::CheckoutProcess(
|
||||
not_null<Main::Session*> session,
|
||||
FullMsgId itemId,
|
||||
PrivateTag)
|
||||
: _session(session)
|
||||
, _form(std::make_unique<Form>(session, itemId))
|
||||
, _panel(std::make_unique<Ui::Panel>(panelDelegate())) {
|
||||
_form->updates(
|
||||
) | rpl::start_with_next([=](const FormUpdate &update) {
|
||||
handleFormUpdate(update);
|
||||
}, _lifetime);
|
||||
_panel->backRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
showForm();
|
||||
}, _panel->lifetime());
|
||||
showForm();
|
||||
}
|
||||
|
||||
CheckoutProcess::~CheckoutProcess() {
|
||||
}
|
||||
|
||||
void CheckoutProcess::requestActivate() {
|
||||
_panel->requestActivate();
|
||||
}
|
||||
|
||||
not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() {
|
||||
return static_cast<PanelDelegate*>(this);
|
||||
}
|
||||
|
||||
void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
|
||||
v::match(update, [&](const FormReady &) {
|
||||
performInitialSilentValidation();
|
||||
if (!_initialSilentValidation) {
|
||||
showForm();
|
||||
}
|
||||
}, [&](const ThumbnailUpdated &data) {
|
||||
_panel->updateFormThumbnail(data.thumbnail);
|
||||
}, [&](const ValidateFinished &) {
|
||||
if (_initialSilentValidation) {
|
||||
_initialSilentValidation = false;
|
||||
}
|
||||
showForm();
|
||||
if (_submitState == SubmitState::Validation) {
|
||||
_submitState = SubmitState::Validated;
|
||||
panelSubmit();
|
||||
}
|
||||
}, [&](const PaymentMethodUpdate &) {
|
||||
showForm();
|
||||
}, [&](const VerificationNeeded &data) {
|
||||
if (!_panel->showWebview(data.url, false)) {
|
||||
File::OpenUrl(data.url);
|
||||
panelCloseSure();
|
||||
}
|
||||
}, [&](const PaymentFinished &data) {
|
||||
const auto weak = base::make_weak(this);
|
||||
_session->api().applyUpdates(data.updates);
|
||||
if (weak) {
|
||||
panelCloseSure();
|
||||
}
|
||||
}, [&](const Error &error) {
|
||||
handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
void CheckoutProcess::handleError(const Error &error) {
|
||||
const auto showToast = [&](const TextWithEntities &text) {
|
||||
if (_panel) {
|
||||
_panel->requestActivate();
|
||||
_panel->showToast(text);
|
||||
} else {
|
||||
App::wnd()->activate();
|
||||
Ui::ShowMultilineToast({ .text = text });
|
||||
}
|
||||
};
|
||||
const auto &id = error.id;
|
||||
switch (error.type) {
|
||||
case Error::Type::Form:
|
||||
if (true
|
||||
|| id == u"PROVIDER_ACCOUNT_INVALID"_q
|
||||
|| id == u"PROVIDER_ACCOUNT_TIMEOUT"_q) {
|
||||
showToast({ "Error: " + id });
|
||||
}
|
||||
break;
|
||||
case Error::Type::Validate: {
|
||||
if (_submitState == SubmitState::Validation) {
|
||||
_submitState = SubmitState::None;
|
||||
}
|
||||
if (_initialSilentValidation) {
|
||||
_initialSilentValidation = false;
|
||||
showForm();
|
||||
return;
|
||||
}
|
||||
using InfoField = Ui::InformationField;
|
||||
using CardField = Ui::CardField;
|
||||
if (id == u"REQ_INFO_NAME_INVALID"_q) {
|
||||
showInformationError(InfoField::Name);
|
||||
} else if (id == u"REQ_INFO_EMAIL_INVALID"_q) {
|
||||
showInformationError(InfoField::Email);
|
||||
} else if (id == u"REQ_INFO_PHONE_INVALID"_q) {
|
||||
showInformationError(InfoField::Phone);
|
||||
} else if (id == u"ADDRESS_STREET_LINE1_INVALID"_q) {
|
||||
showInformationError(InfoField::ShippingStreet);
|
||||
} else if (id == u"ADDRESS_CITY_INVALID"_q) {
|
||||
showInformationError(InfoField::ShippingCity);
|
||||
} else if (id == u"ADDRESS_STATE_INVALID"_q) {
|
||||
showInformationError(InfoField::ShippingState);
|
||||
} else if (id == u"ADDRESS_COUNTRY_INVALID"_q) {
|
||||
showInformationError(InfoField::ShippingCountry);
|
||||
} else if (id == u"ADDRESS_POSTCODE_INVALID"_q) {
|
||||
showInformationError(InfoField::ShippingPostcode);
|
||||
} else if (id == u"LOCAL_CARD_NUMBER_INVALID"_q) {
|
||||
showCardError(CardField::Number);
|
||||
} else if (id == u"LOCAL_CARD_EXPIRE_DATE_INVALID"_q) {
|
||||
showCardError(CardField::ExpireDate);
|
||||
} else if (id == u"LOCAL_CARD_CVC_INVALID"_q) {
|
||||
showCardError(CardField::Cvc);
|
||||
} else if (id == u"LOCAL_CARD_HOLDER_NAME_INVALID"_q) {
|
||||
showCardError(CardField::Name);
|
||||
} else if (id == u"LOCAL_CARD_BILLING_COUNTRY_INVALID"_q) {
|
||||
showCardError(CardField::AddressCountry);
|
||||
} else if (id == u"LOCAL_CARD_BILLING_ZIP_INVALID"_q) {
|
||||
showCardError(CardField::AddressZip);
|
||||
} else if (id == u"SHIPPING_BOT_TIMEOUT"_q) {
|
||||
showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message
|
||||
} else if (id == u"SHIPPING_NOT_AVAILABLE"_q) {
|
||||
showToast({ "Error: Shipping to the selected country is not available!" }); // #TODO payments errors message
|
||||
} else {
|
||||
showToast({ "Error: " + id });
|
||||
}
|
||||
} break;
|
||||
case Error::Type::Stripe: {
|
||||
using Field = Ui::CardField;
|
||||
if (id == u"InvalidNumber"_q || id == u"IncorrectNumber"_q) {
|
||||
showCardError(Field::Number);
|
||||
} else if (id == u"InvalidCVC"_q || id == u"IncorrectCVC"_q) {
|
||||
showCardError(Field::Cvc);
|
||||
} else if (id == u"InvalidExpiryMonth"_q
|
||||
|| id == u"InvalidExpiryYear"_q
|
||||
|| id == u"ExpiredCard"_q) {
|
||||
showCardError(Field::ExpireDate);
|
||||
} else if (id == u"CardDeclined"_q) {
|
||||
// #TODO payments errors message
|
||||
showToast({ "Error: " + id });
|
||||
} else if (id == u"ProcessingError"_q) {
|
||||
// #TODO payments errors message
|
||||
showToast({ "Error: " + id });
|
||||
} else {
|
||||
showToast({ "Error: " + id });
|
||||
}
|
||||
} break;
|
||||
case Error::Type::Send:
|
||||
if (_submitState == SubmitState::Finishing) {
|
||||
_submitState = SubmitState::None;
|
||||
}
|
||||
if (id == u"PAYMENT_FAILED"_q) {
|
||||
showToast({ "Error: Payment Failed. Your card has not been billed." }); // #TODO payments errors message
|
||||
} else if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
|
||||
showToast({ "Error: PreCheckout Failed. Your card has not been billed." }); // #TODO payments errors message
|
||||
} else if (id == u"REQUESTED_INFO_INVALID"_q
|
||||
|| id == u"SHIPPING_OPTION_INVALID"_q
|
||||
|| id == u"PAYMENT_CREDENTIALS_INVALID"_q
|
||||
|| id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) {
|
||||
showToast({ "Error: " + id + ". Your card has not been billed." });
|
||||
}
|
||||
break;
|
||||
default: Unexpected("Error type in CheckoutProcess::handleError.");
|
||||
}
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelRequestClose() {
|
||||
panelCloseSure(); // #TODO payments confirmation
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelCloseSure() {
|
||||
const auto i = Processes.find(_session);
|
||||
if (i == end(Processes)) {
|
||||
return;
|
||||
}
|
||||
const auto j = ranges::find(i->second.map, this, [](const auto &pair) {
|
||||
return pair.second.get();
|
||||
});
|
||||
if (j == end(i->second.map)) {
|
||||
return;
|
||||
}
|
||||
i->second.map.erase(j);
|
||||
if (i->second.map.empty()) {
|
||||
Processes.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelSubmit() {
|
||||
if (_submitState == SubmitState::Validation
|
||||
|| _submitState == SubmitState::Finishing) {
|
||||
return;
|
||||
}
|
||||
const auto &method = _form->paymentMethod();
|
||||
const auto &invoice = _form->invoice();
|
||||
const auto &options = _form->shippingOptions();
|
||||
if (!options.list.empty() && options.selectedId.isEmpty()) {
|
||||
chooseShippingOption();
|
||||
return;
|
||||
} else if (_submitState != SubmitState::Validated
|
||||
&& options.list.empty()
|
||||
&& (invoice.isShippingAddressRequested
|
||||
|| invoice.isNameRequested
|
||||
|| invoice.isEmailRequested
|
||||
|| invoice.isPhoneRequested)) {
|
||||
_submitState = SubmitState::Validation;
|
||||
_form->validateInformation(_form->savedInformation());
|
||||
return;
|
||||
} else if (!method.newCredentials && !method.savedCredentials) {
|
||||
editPaymentMethod();
|
||||
return;
|
||||
}
|
||||
_submitState = SubmitState::Finishing;
|
||||
_form->submit();
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) {
|
||||
if (!message.isArray()) {
|
||||
LOG(("Payments Error: "
|
||||
"Not an array received in buy_callback arguments."));
|
||||
return;
|
||||
}
|
||||
const auto list = message.array();
|
||||
if (list.at(0).toString() != "payment_form_submit") {
|
||||
return;
|
||||
} else if (!list.at(1).isString()) {
|
||||
LOG(("Payments Error: "
|
||||
"Not a string received in buy_callback result."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = QJsonParseError();
|
||||
const auto document = QJsonDocument::fromJson(
|
||||
list.at(1).toString().toUtf8(),
|
||||
&error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("Payments Error: "
|
||||
"Failed to parse buy_callback arguments, error: %1."
|
||||
).arg(error.errorString()));
|
||||
return;
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("Payments Error: "
|
||||
"Not an object decoded in buy_callback result."));
|
||||
return;
|
||||
}
|
||||
const auto root = document.object();
|
||||
const auto title = root.value("title").toString();
|
||||
const auto credentials = root.value("credentials");
|
||||
if (!credentials.isObject()) {
|
||||
LOG(("Payments Error: "
|
||||
"Not an object received in payment credentials."));
|
||||
return;
|
||||
}
|
||||
crl::on_main(this, [=] {
|
||||
_form->setPaymentCredentials(NewCredentials{
|
||||
.title = title,
|
||||
.data = QJsonDocument(
|
||||
credentials.toObject()
|
||||
).toJson(QJsonDocument::Compact),
|
||||
.saveOnServer = false, // #TODO payments save
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
|
||||
if (Core::TryConvertUrlToLocal(uri) == uri) {
|
||||
return true;
|
||||
}
|
||||
crl::on_main(this, [=] {
|
||||
panelCloseSure();
|
||||
App::wnd()->activate();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelEditPaymentMethod() {
|
||||
if (_submitState != SubmitState::None
|
||||
&& _submitState != SubmitState::Validated) {
|
||||
return;
|
||||
}
|
||||
editPaymentMethod();
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelValidateCard(Ui::UncheckedCardDetails data) {
|
||||
_form->validateCard(data);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelEditShippingInformation() {
|
||||
showEditInformation(Ui::InformationField::ShippingStreet);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelEditName() {
|
||||
showEditInformation(Ui::InformationField::Name);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelEditEmail() {
|
||||
showEditInformation(Ui::InformationField::Email);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelEditPhone() {
|
||||
showEditInformation(Ui::InformationField::Phone);
|
||||
}
|
||||
|
||||
void CheckoutProcess::showForm() {
|
||||
_panel->showForm(
|
||||
_form->invoice(),
|
||||
_form->savedInformation(),
|
||||
_form->paymentMethod().ui,
|
||||
_form->shippingOptions());
|
||||
}
|
||||
|
||||
void CheckoutProcess::showEditInformation(Ui::InformationField field) {
|
||||
if (_submitState != SubmitState::None) {
|
||||
return;
|
||||
}
|
||||
_panel->showEditInformation(
|
||||
_form->invoice(),
|
||||
_form->savedInformation(),
|
||||
field);
|
||||
}
|
||||
|
||||
void CheckoutProcess::showInformationError(Ui::InformationField field) {
|
||||
if (_submitState != SubmitState::None) {
|
||||
return;
|
||||
}
|
||||
_panel->showInformationError(
|
||||
_form->invoice(),
|
||||
_form->savedInformation(),
|
||||
field);
|
||||
}
|
||||
|
||||
void CheckoutProcess::showCardError(Ui::CardField field) {
|
||||
if (_submitState != SubmitState::None) {
|
||||
return;
|
||||
}
|
||||
_panel->showCardError(_form->paymentMethod().ui.native, field);
|
||||
}
|
||||
|
||||
void CheckoutProcess::chooseShippingOption() {
|
||||
_panel->chooseShippingOption(_form->shippingOptions());
|
||||
}
|
||||
|
||||
void CheckoutProcess::editPaymentMethod() {
|
||||
_panel->choosePaymentMethod(_form->paymentMethod().ui);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelChooseShippingOption() {
|
||||
if (_submitState != SubmitState::None) {
|
||||
return;
|
||||
}
|
||||
chooseShippingOption();
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelChangeShippingOption(const QString &id) {
|
||||
_form->setShippingOption(id);
|
||||
showForm();
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelValidateInformation(
|
||||
Ui::RequestedInformation data) {
|
||||
_form->validateInformation(data);
|
||||
}
|
||||
|
||||
void CheckoutProcess::panelShowBox(object_ptr<Ui::BoxContent> box) {
|
||||
_panel->showBox(std::move(box));
|
||||
}
|
||||
|
||||
void CheckoutProcess::performInitialSilentValidation() {
|
||||
const auto &invoice = _form->invoice();
|
||||
const auto &saved = _form->savedInformation();
|
||||
if (invoice.receipt
|
||||
|| (invoice.isNameRequested && saved.name.isEmpty())
|
||||
|| (invoice.isEmailRequested && saved.email.isEmpty())
|
||||
|| (invoice.isPhoneRequested && saved.phone.isEmpty())
|
||||
|| (invoice.isShippingAddressRequested && !saved.shippingAddress)) {
|
||||
return;
|
||||
}
|
||||
_initialSilentValidation = true;
|
||||
_form->validateInformation(saved);
|
||||
}
|
||||
|
||||
QString CheckoutProcess::panelWebviewDataPath() {
|
||||
return _session->domain().local().webviewDataPath();
|
||||
}
|
||||
|
||||
} // namespace Payments
|
||||