Compare commits

..

81 Commits

Author SHA1 Message Date
John Preston
a82d1e863e Version 2.7.1: Fix channels ban in admin log. 2021-03-20 18:19:31 +04:00
John Preston
26d97a3636 Version 2.7.1.
- Fix editing 'Manage Voice Chats' rights for channel admins.
- Fix verification check display in voice chat participants list.
- Allow removing and blocking channels from voice chats.
2021-03-20 16:23:41 +04:00
John Preston
7b8e421996 Allow markup in some voice chat toasts. 2021-03-20 16:23:41 +04:00
John Preston
2bc2a0e459 Fix possible integer overflow. 2021-03-20 16:03:58 +04:00
John Preston
7cb4b4f8ab Don't try to use WebRTC built-in audio backends. 2021-03-20 15:43:35 +04:00
John Preston
b439ecce16 Allow all toasts to be multiline in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a33a4c0589 Fix maximize/restore voice chat title bar button. 2021-03-20 15:43:35 +04:00
John Preston
5278e2201f Make red 'Remove' in voice chat participant menu. 2021-03-20 15:43:35 +04:00
John Preston
3bd6b2268f Allow blocking channels in voice chats. 2021-03-20 15:43:35 +04:00
John Preston
a0a13c3b86 Update API scheme to layer 126. 2021-03-20 15:43:35 +04:00
John Preston
0052c7938f Fix verified icon in voice chat participants list. 2021-03-20 15:43:35 +04:00
John Preston
a14db3e492 Allow editing 'Manage Voice Chats' admin right in channels. 2021-03-20 15:43:35 +04:00
Ilya Fedin
7979b3b6c8 Fix devtoolset version in linux action 2021-03-20 14:33:02 +03:00
Ilya Fedin
3f25e92afd Add build options for libtgvoip backends
libtgvoip has options to disbale some backends, but they never were exposed via tdesktop's cmake

Since libtgvoip autoconf build system doesn't work anymore, it's worth to have these options in tdesktop's cmake.
2021-03-20 14:33:02 +03:00
Ilya Fedin
3d1cddaca5 Add a way to change default handler in snap 2021-03-20 14:20:09 +03:00
John Preston
eeecc42c25 Version 2.7.
- Start limitless Voice Chats in Groups and Channels.
- Host discussions that can be listened to
by millions of people simultaneously.
- Record voice chats to share or publish in Channels later.
- See that a chat is being recorded
from the red dot next to its title.
- See user bio texts right from the list of participants.
- Raise your hand to show admins you want to speak.
- Create separate Voice Chat Invite Links for listeners or speakers.
- Change the title of your Voice Chat
to give people an idea of the current topic.
- Join Voice Chats as one of your Channels
to hide your personal account.
2021-03-19 14:15:26 +04:00
John Preston
e22ecafc1d Add confirmation on create / anonymous admin join. 2021-03-19 14:10:02 +04:00
John Preston
ba41da7b28 Fix discarded group call handle. 2021-03-19 00:57:16 +04:00
John Preston
9cfbccf9e7 Beta version 2.6.8.
- Fix connecting and getting allowed to speak on voice chats.
- MPRIS support on Linux.
2021-03-18 22:56:42 +04:00
John Preston
2b6f50e114 Fix joining / unmuting. 2021-03-18 22:56:42 +04:00
Ilya Fedin
d2f57b72c3 Add mpris permission for snap 2021-03-18 21:55:06 +03:00
Ilya Fedin
85ac983a27 Add MPRIS support 2021-03-18 21:55:06 +03:00
John Preston
ac397e6e19 Beta version 2.6.7.
- Improve voice chat participants list updating.
2021-03-18 18:05:43 +04:00
John Preston
38e15c9bdb Fix saving legacy chat admins without migration.
Fixes #10558.
2021-03-18 17:27:33 +04:00
John Preston
00d65fa978 Request one participants slice on voice chat reload. 2021-03-18 16:58:05 +04:00
John Preston
3fea9cca08 Subscribe to channel updates in voice chat. 2021-03-18 15:54:28 +04:00
John Preston
b390e0766b Apply all queued updates on reload. 2021-03-18 15:30:58 +04:00
John Preston
2f75e6bbe2 Add some logging for voice chat updates. 2021-03-18 15:22:55 +04:00
Ilya Fedin
decbbb9a73 Check for openal fork updates in Dockerfile 2021-03-18 07:51:57 +03:00
Ilya Fedin
b4b80822c8 Set glib's application name and prgname 2021-03-18 07:51:57 +03:00
John Preston
bc82cdc3b3 Call dump_syms and strip outside of docker. 2021-03-18 02:33:29 +04:00
John Preston
ebc67d25f0 Migrate docker build to GCC 9. 2021-03-18 00:30:12 +04:00
John Preston
348b4d54ba Revert "Workaround build issues on GCC."
This reverts commit 3defb06783.
2021-03-18 00:30:12 +04:00
John Preston
6f86ce595b Beta version 2.6.6.
- Fix joining popular voice chats.
2021-03-18 00:26:53 +04:00
John Preston
8c53a3c19e Don't skip updateGroupCallParticipants while in getDifference. 2021-03-18 00:25:53 +04:00
John Preston
67623072d6 Fix joining a voice chat. 2021-03-18 00:24:36 +04:00
John Preston
1291f1c80d Beta version 2.6.5.
- Improvements and fixes in new voice chat features.
2021-03-17 21:16:55 +04:00
John Preston
0684db9bd8 Improve participants sorting in voice chats. 2021-03-17 20:37:55 +04:00
John Preston
db7b61a77b Rewrite voice chat members list management. 2021-03-17 20:37:55 +04:00
John Preston
d392633b90 Send speaking typings in channels. 2021-03-17 20:37:55 +04:00
John Preston
76e08af26a Apply updateGroupCallParticipants before updateGroupCall. 2021-03-17 20:37:55 +04:00
John Preston
b23f16e6e4 Don't show 'allowed to speak' on first join. 2021-03-17 20:37:54 +04:00
23rd
23156d523c Fixed Github CI Windows build. 2021-03-17 18:59:20 +03:00
Ilya Fedin
04b0e2e9e6 Update submodules 2021-03-17 18:58:02 +03:00
Ilya Fedin
ace5740125 Use QProcess::startDetached for xdg-open
Since it may running continously
2021-03-17 15:39:26 +03:00
John Preston
bc67b79023 Beta version 2.6.4: 110% UI scale on macOS Retina. 2021-03-17 00:07:08 +04:00
John Preston
528c98af67 Beta version 2.6.4.
- Fix freeze in voice chats.
2021-03-17 00:02:05 +04:00
John Preston
311a2f2753 Fix freeze in voice chats. 2021-03-16 23:54:58 +04:00
John Preston
3defb06783 Workaround build issues on GCC. 2021-03-16 22:26:58 +04:00
John Preston
5708b5e849 Fix confirmation when joining by link. 2021-03-16 21:06:36 +04:00
John Preston
1db1328a91 Beta version 2.6.3.
- Fix audio device selection in voice chats.
- Fix blinking self profile photo
in case the profile photo privacy is used.
- Fix voice chat admin menu on macOS.
2021-03-16 19:13:15 +04:00
John Preston
2e9d6d73c3 Fix invalid 'You can speak' notification. 2021-03-16 19:09:23 +04:00
John Preston
38dd5ab837 Fix 'join as' userpic button display. 2021-03-16 18:58:10 +04:00
John Preston
83ab670c50 Remove 'wants to speak' status in three seconds. 2021-03-16 18:51:38 +04:00
John Preston
5621e41529 Limit voice chat title to 40 characters. 2021-03-16 18:51:11 +04:00
John Preston
056cab6268 Don't apply private userpic 'min' updates. 2021-03-16 18:15:19 +04:00
John Preston
61d0d240aa Add 'rejoin as' and 'change title' toast notifications. 2021-03-16 18:13:51 +04:00
John Preston
33ae4c2802 Improve layout of voice chat channel choosing. 2021-03-16 17:16:56 +04:00
John Preston
2c806b11d7 Always join voice chats by link with confirmation. 2021-03-16 17:16:01 +04:00
John Preston
199434c7a2 Add recording mark animation. 2021-03-16 14:48:12 +04:00
John Preston
c65c554d88 Don't ask joinAs for voice chats each time. 2021-03-16 14:33:11 +04:00
John Preston
5d16359a5a Fix voice chat three-dot menu on macOS. 2021-03-16 13:50:40 +04:00
John Preston
fd9ad04d15 Vazir Without-Latin instead of Without-Latin-UI.
Fixes #10536.
2021-03-16 13:29:46 +04:00
Ilya Fedin
0c8febce9c Avoid allocations for case-insensetive compare 2021-03-16 12:20:48 +03:00
John Preston
4659cc50f2 Allow inviting members in channel voice chats. 2021-03-15 22:40:59 +04:00
John Preston
2fddeb478b Fix long texts in invite link error toasts. 2021-03-15 20:49:38 +04:00
Ilya Fedin
4ffe1d3acc Use base::flat_map for pointer keys 2021-03-15 19:49:06 +03:00
Ilya Fedin
cdf0512515 Remove unused variable in mainwidget 2021-03-15 19:49:06 +03:00
Ilya Fedin
971e188063 Replace remaining QFileInfo::exists 2021-03-15 19:49:06 +03:00
John Preston
a909c1a813 Make default UI scale 120% on macOS Retina. 2021-03-15 20:33:41 +04:00
John Preston
4fc2b1f1a3 Append x64 to app_version on Windows. 2021-03-15 20:14:39 +04:00
John Preston
fb04f33ae8 Fix long voice chat title elision on macOS. 2021-03-15 19:34:58 +04:00
John Preston
b2c87e7a73 Fix three-dot toggle in voice chats. 2021-03-15 19:19:29 +04:00
John Preston
86a33ceea1 Fix audio device selection in voice chats. 2021-03-15 19:00:58 +04:00
John Preston
a5d8d7a550 Add some validation for GroupThumbs. 2021-03-15 19:00:32 +04:00
Ilya Fedin
11723aedff Get rid of empty QStringLiterals 2021-03-15 17:19:59 +03:00
Ilya Fedin
fe5de8f009 Fix non-working UniqueConnection in gtk file dialog 2021-03-15 17:19:44 +03:00
Ilya Fedin
6b68d001ae Get rid of deprecated ranges::action 2021-03-15 11:41:29 +03:00
Ilya Fedin
ae0b9141dd Trying to get rid of unused variables... 2021-03-15 11:40:44 +03:00
Ilya Fedin
12e306dd7b Replace remaining multi-args 2021-03-15 11:21:05 +03:00
Ilya Fedin
508762cd2c Use static QFileInfo::exists 2021-03-15 11:21:05 +03:00
111 changed files with 2836 additions and 1186 deletions

View File

@@ -55,7 +55,7 @@ jobs:
defaults:
run:
shell: scl enable devtoolset-8 -- bash --noprofile --norc -eo pipefail {0}
shell: scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
@@ -65,6 +65,8 @@ jobs:
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
- "DESKTOP_APP_DISABLE_GTK_INTEGRATION"
- "LIBTGVOIP_DISABLE_ALSA"
- "LIBTGVOIP_DISABLE_PULSEAUDIO"
env:
UPLOAD_ARTIFACT: "false"

View File

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

View File

@@ -821,6 +821,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
@@ -1113,6 +1115,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

View File

@@ -1939,6 +1939,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";
@@ -1966,6 +1968,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_copy_listener_link" = "Copy Listener Link";
"lng_group_call_end" = "End Voice Chat";
"lng_group_call_join" = "Join";
"lng_group_call_join_confirm" = "Do you want to join the voice chat {chat}?";
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
@@ -1979,6 +1982,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";
@@ -2011,7 +2016,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_recording_start_button" = "Start";
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
"lng_group_call_can_speak_here" = "You can now speak.";
"lng_group_call_can_speak" = "You can now speak in **{chat}**.";
"lng_group_call_can_speak" = "You can now speak in {chat}.";
"lng_group_call_title_changed" = "Voice chat title changed to {title}";
"lng_group_call_join_as_changed" = "Members of this voice chat will now see you as {name}";
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
@@ -2219,6 +2226,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}";

View File

@@ -620,8 +620,8 @@ channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant;
channelParticipantBanned#50a1dfd6 flags:# left:flags.0?true peer:Peer kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant;
channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
@@ -632,10 +632,10 @@ channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter;
channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipants#9ab0feaf count:int participants:Vector<ChannelParticipant> chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipant;
help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
@@ -1203,7 +1203,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall;
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
@@ -1557,7 +1557,7 @@ channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = mes
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
@@ -1572,8 +1572,8 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> =
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_groupcall:flags.2?true = messages.Chats;
channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates;
channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
@@ -1645,4 +1645,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 125
// LAYER 126

View File

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

View File

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

View File

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

View File

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

View File

@@ -277,12 +277,32 @@ void Updates::checkLastUpdate(bool afterSleep) {
void Updates::feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds) {
for (const auto &update : updates.v) {
if (skipMessageIds && update.type() == mtpc_updateMessageID) {
SkipUpdatePolicy policy) {
auto list = updates.v;
const auto hasGroupCallParticipantUpdates = ranges::contains(
list,
mtpc_updateGroupCallParticipants,
&MTPUpdate::type);
if (hasGroupCallParticipantUpdates) {
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
if (entry.type() == mtpc_updateGroupCallParticipants) {
return 0;
} else {
return 1;
}
});
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
return;
}
for (const auto &entry : std::as_const(list)) {
const auto type = entry.type();
if ((policy == SkipUpdatePolicy::SkipMessageIds
&& type == mtpc_updateMessageID)
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
&& type != mtpc_updateGroupCallParticipants)) {
continue;
}
feedUpdate(update);
feedUpdate(entry);
}
session().data().sendHistoryChangeNotifications();
}
@@ -392,7 +412,9 @@ void Updates::feedChannelDifference(
session().data().processMessages(
data.vnew_messages(),
NewMessageType::Unread);
feedUpdateVector(data.vother_updates(), true);
feedUpdateVector(
data.vother_updates(),
SkipUpdatePolicy::SkipMessageIds);
_handlingChannelDifference = false;
}
@@ -553,14 +575,14 @@ 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) {
LOG(("RPC Error in getDifference: %1 %2: %3"
).arg(error.code()
).arg(error.type()
).arg(error.description()));
LOG(("RPC Error in getDifference: %1 %2: %3").arg(
QString::number(error.code()),
error.type(),
error.description()));
failDifferenceStartTimerFor(nullptr);
}
@@ -810,9 +832,32 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
if (!requestingDifference()
|| HasForceLogoutNotification(updates)) {
applyUpdates(updates);
} else {
applyGroupCallParticipantUpdates(updates);
}
}
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
updates.match([&](const MTPDupdates &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdatesCombined &data) {
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
feedUpdateVector(
data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdateShort &data) {
if (data.vupdate().type() == mtpc_updateGroupCallParticipants) {
feedUpdate(data.vupdate());
}
}, [](const auto &) {
});
}
int32 Updates::pts() const {
return _ptsWaiter.current();
}

View File

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

View File

@@ -1522,13 +1522,20 @@ void ApiWrap::applyLastParticipantsList(
auto botStatus = channel->mgInfo->botStatus;
const auto emptyAdminRights = MTP_chatAdminRights(MTP_flags(0));
const auto emptyRestrictedRights = MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0));
for (const auto &p : list) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
if (!participantId) {
continue;
}
const auto participant = _session->data().peer(participantId);
const auto user = participant->asUser();
const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin)
? p.c_channelParticipantAdmin().is_can_edit()
: (p.type() == mtpc_channelParticipantCreator)
@@ -1541,28 +1548,27 @@ void ApiWrap::applyLastParticipantsList(
: emptyAdminRights;
const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned)
? p.c_channelParticipantBanned().vbanned_rights()
: emptyRestrictedRights;
if (!userId) {
continue;
}
auto user = _session->data().user(userId);
: ChannelData::EmptyRestrictedRights(participant);
if (p.type() == mtpc_channelParticipantCreator) {
Assert(user != nullptr);
const auto &creator = p.c_channelParticipantCreator();
const auto rank = qs(creator.vrank().value_or_empty());
channel->mgInfo->creator = user;
channel->mgInfo->creatorRank = rank;
if (!channel->mgInfo->admins.empty()) {
Data::ChannelAdminChanges(channel).add(userId, rank);
Data::ChannelAdminChanges(channel).add(
peerToUser(participantId),
rank);
}
}
if (!base::contains(channel->mgInfo->lastParticipants, user)) {
if (user
&& !base::contains(channel->mgInfo->lastParticipants, user)) {
channel->mgInfo->lastParticipants.push_back(user);
if (adminRights.c_chatAdminRights().vflags().v) {
channel->mgInfo->lastAdmins.emplace(
user,
MegagroupInfo::Admin{ adminRights, adminCanEdit });
} else if (restrictedRights.c_chatBannedRights().vflags().v != 0) {
} else if (Data::ChatBannedRightsFlags(restrictedRights) != 0) {
channel->mgInfo->lastRestricted.emplace(
user,
MegagroupInfo::Restricted{ restrictedRights });
@@ -1607,22 +1613,29 @@ void ApiWrap::applyBotsList(
auto botStatus = channel->mgInfo->botStatus;
auto keyboardBotFound = !history || !history->lastKeyboardFrom;
for (const auto &p : list) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
if (!userId) {
if (!participantId) {
continue;
}
auto user = _session->data().user(userId);
if (user->isBot()) {
const auto participant = _session->data().peer(participantId);
const auto user = participant->asUser();
if (user && user->isBot()) {
channel->mgInfo->bots.insert(user);
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
if (!user->botInfo->inited) {
needBotsInfos = true;
}
}
if (!keyboardBotFound && user->id == history->lastKeyboardFrom) {
if (!keyboardBotFound
&& participant->id == history->lastKeyboardFrom) {
keyboardBotFound = true;
}
}
@@ -1657,7 +1670,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
_selfParticipantRequests.emplace(channel);
request(MTPchannels_GetParticipant(
channel->inputChannel,
MTP_inputUserSelf()
MTP_inputPeerSelf()
)).done([=](const MTPchannels_ChannelParticipant &result) {
_selfParticipantRequests.erase(channel);
result.match([&](const MTPDchannels_channelParticipant &data) {
@@ -1698,11 +1711,13 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> channel) {
void ApiWrap::kickParticipant(
not_null<ChatData*> chat,
not_null<UserData*> user) {
not_null<PeerData*> participant) {
Expects(participant->isUser());
request(MTPmessages_DeleteChatUser(
MTP_flags(0),
chat->inputChat,
user->inputUser
participant->asUser()->inputUser
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).send();
@@ -1710,21 +1725,21 @@ void ApiWrap::kickParticipant(
void ApiWrap::kickParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &currentRights) {
const auto kick = KickRequest(channel, user);
const auto kick = KickRequest(channel, participant);
if (_kickRequests.contains(kick)) return;
const auto rights = ChannelData::KickedRestrictedRights();
const auto rights = ChannelData::KickedRestrictedRights(participant);
const auto requestId = request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
participant->input,
rights
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
_kickRequests.remove(KickRequest(channel, user));
channel->applyEditBanned(user, currentRights, rights);
_kickRequests.remove(KickRequest(channel, participant));
channel->applyEditBanned(participant, currentRights, rights);
}).fail([this, kick](const MTP::Error &error) {
_kickRequests.remove(kick);
}).send();
@@ -1734,20 +1749,20 @@ void ApiWrap::kickParticipant(
void ApiWrap::unblockParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user) {
const auto kick = KickRequest(channel, user);
not_null<PeerData*> participant) {
const auto kick = KickRequest(channel, participant);
if (_kickRequests.contains(kick)) {
return;
}
const auto requestId = request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
MTP_chatBannedRights(MTP_flags(0), MTP_int(0))
participant->input,
ChannelData::EmptyRestrictedRights(participant)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
_kickRequests.remove(KickRequest(channel, user));
_kickRequests.remove(KickRequest(channel, participant));
if (channel->kickedCount() > 0) {
channel->setKickedCount(channel->kickedCount() - 1);
} else {
@@ -2600,7 +2615,7 @@ void ApiWrap::clearWebPageRequests() {
void ApiWrap::resolveWebPages() {
auto ids = QVector<MTPInputMessage>(); // temp_req_id = -1
using IndexAndMessageIds = QPair<int32, QVector<MTPInputMessage>>;
using MessageIdsByChannel = QMap<ChannelData*, IndexAndMessageIds>;
using MessageIdsByChannel = base::flat_map<ChannelData*, IndexAndMessageIds>;
MessageIdsByChannel idsByChannel; // temp_req_id = -index - 2
ids.reserve(_webPagesPending.size());
@@ -2617,18 +2632,18 @@ void ApiWrap::resolveWebPages() {
auto channel = item->history()->peer->asChannel();
auto channelMap = idsByChannel.find(channel);
if (channelMap == idsByChannel.cend()) {
channelMap = idsByChannel.insert(
channelMap = idsByChannel.emplace(
channel,
IndexAndMessageIds(
idsByChannel.size(),
QVector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(item->id)))));
MTP_inputMessageID(MTP_int(item->id))))).first;
} else {
channelMap.value().second.push_back(
channelMap->second.second.push_back(
MTP_inputMessageID(MTP_int(item->id)));
}
i.value() = -channelMap.value().first - 2;
i.value() = -channelMap->second.first - 2;
}
}
} else {
@@ -2648,10 +2663,10 @@ void ApiWrap::resolveWebPages() {
}
QVector<mtpRequestId> reqsByIndex(idsByChannel.size(), 0);
for (auto i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) {
reqsByIndex[i.value().first] = request(MTPchannels_GetMessages(
i.key()->inputChannel,
MTP_vector<MTPInputMessage>(i.value().second)
)).done([=, channel = i.key()](
reqsByIndex[i->second.first] = request(MTPchannels_GetMessages(
i->first->inputChannel,
MTP_vector<MTPInputMessage>(i->second.second)
)).done([=, channel = i->first](
const MTPmessages_Messages &result,
mtpRequestId requestId) {
gotWebPages(channel, result, requestId);
@@ -3183,12 +3198,20 @@ void ApiWrap::refreshChannelAdmins(
const QVector<MTPChannelParticipant> &participants) {
Data::ChannelAdminChanges changes(channel);
for (const auto &p : participants) {
const auto userId = p.match([](const auto &data) {
return data.vuser_id().v;
const auto participantId = p.match([](
const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
const auto userId = peerToUser(participantId);
p.match([&](const MTPDchannelParticipantAdmin &data) {
Assert(peerIsUser(participantId));
changes.add(userId, qs(data.vrank().value_or_empty()));
}, [&](const MTPDchannelParticipantCreator &data) {
Assert(peerIsUser(participantId));
const auto rank = qs(data.vrank().value_or_empty());
if (const auto info = channel->mgInfo.get()) {
info->creator = channel->owner().userLoaded(userId);
@@ -3196,7 +3219,9 @@ void ApiWrap::refreshChannelAdmins(
}
changes.add(userId, rank);
}, [&](const auto &data) {
changes.remove(userId);
if (userId) {
changes.remove(userId);
}
});
}
}

View File

@@ -249,14 +249,16 @@ public:
void markMediaRead(not_null<HistoryItem*> item);
void requestSelfParticipant(not_null<ChannelData*> channel);
void kickParticipant(not_null<ChatData*> chat, not_null<UserData*> user);
void kickParticipant(
not_null<ChatData*> chat,
not_null<PeerData*> participant);
void kickParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &currentRights);
void unblockParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user);
not_null<PeerData*> participant);
void deleteAllFromUser(
not_null<ChannelData*> channel,
not_null<UserData*> from);
@@ -657,7 +659,7 @@ private:
using KickRequest = std::pair<
not_null<ChannelData*>,
not_null<UserData*>>;
not_null<PeerData*>>;
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
base::flat_set<not_null<ChannelData*>> _selfParticipantRequests;

View File

@@ -111,7 +111,7 @@ confirmInviteUserName: FlatLabel(defaultFlatLabel) {
confirmInviteUserNameTop: 227px;
confirmPhoneAboutLabel: FlatLabel(defaultFlatLabel) {
minWidth: 282px;
minWidth: 272px;
}
confirmPhoneCodeField: InputField(defaultInputField) {
}

View File

@@ -900,7 +900,7 @@ void DeleteMessagesBox::deleteAndClear() {
_moderateInChannel->session().api().kickParticipant(
_moderateInChannel,
_moderateFrom,
MTP_chatBannedRights(MTP_flags(0), MTP_int(0)));
ChannelData::EmptyRestrictedRights(_moderateFrom));
}
if (_reportSpam->checked()) {
_moderateInChannel->session().api().request(

View File

@@ -464,7 +464,6 @@ void Rows::showMenu(int index) {
Fn<void()> callback) {
return _menu->addAction(text, std::move(callback));
};
const auto id = row->data.id;
if (canShare(row)) {
addAction(tr::lng_proxy_edit_share(tr::now), [=] { share(row); });
}

View File

@@ -695,8 +695,6 @@ bool PasscodeBox::handleCustomCheckError(const MTP::Error &error) {
void PasscodeBox::sendClearCloudPassword(
const Core::CloudPasswordResult &check) {
const auto newPasswordData = QByteArray();
const auto newPasswordHash = QByteArray();
const auto hint = QString();
const auto email = QString();
const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo

View File

@@ -421,6 +421,7 @@ void AddSpecialBoxController::rebuildChatRows(not_null<ChatData*> chat) {
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
Assert(row->peer()->isUser());
auto user = row->peer()->asUser();
if (participants.contains(user)) {
++i;
@@ -467,8 +468,9 @@ void AddSpecialBoxController::loadMoreRows() {
int availableCount,
const QVector<MTPChannelParticipant> &list) {
for (const auto &data : list) {
if (const auto user = _additional.applyParticipant(data)) {
appendRow(user);
if (const auto participant = _additional.applyParticipant(
data)) {
appendRow(participant);
}
}
if (const auto size = list.size()) {
@@ -491,20 +493,25 @@ void AddSpecialBoxController::loadMoreRows() {
}
void AddSpecialBoxController::rowClicked(not_null<PeerListRow*> row) {
auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
switch (_role) {
case Role::Admins: return showAdmin(user);
case Role::Restricted: return showRestricted(user);
case Role::Kicked: return kickUser(user);
case Role::Admins:
Assert(user != nullptr);
return showAdmin(user);
case Role::Restricted:
Assert(user != nullptr);
return showRestricted(user);
case Role::Kicked: return kickUser(participant);
}
Unexpected("Role in AddSpecialBoxController::rowClicked()");
}
template <typename Callback>
bool AddSpecialBoxController::checkInfoLoaded(
not_null<UserData*> user,
not_null<PeerData*> participant,
Callback callback) {
if (_additional.infoLoaded(user)) {
if (_additional.infoLoaded(participant)) {
return true;
}
@@ -512,16 +519,15 @@ bool AddSpecialBoxController::checkInfoLoaded(
const auto channel = _peer->asChannel();
_api.request(MTPchannels_GetParticipant(
channel->inputChannel,
user->inputUser
participant->input
)).done([=](const MTPchannels_ChannelParticipant &result) {
Expects(result.type() == mtpc_channels_channelParticipant);
const auto &participant = result.c_channels_channelParticipant();
channel->owner().processUsers(participant.vusers());
_additional.applyParticipant(participant.vparticipant());
result.match([&](const MTPDchannels_channelParticipant &data) {
channel->owner().processUsers(data.vusers());
_additional.applyParticipant(data.vparticipant());
});
callback();
}).fail([=](const MTP::Error &error) {
_additional.setExternal(user);
_additional.setExternal(participant);
callback();
}).send();
return false;
@@ -724,15 +730,13 @@ void AddSpecialBoxController::showRestricted(
// Finally edit the restricted.
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(user);
auto box = Box<EditRestrictedBox>(
_peer,
user,
_additional.adminRights(user).has_value(),
currentRights);
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
@@ -749,50 +753,61 @@ void AddSpecialBoxController::showRestricted(
}
void AddSpecialBoxController::editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
if (_editParticipantBox) {
_editParticipantBox->closeBox();
}
const auto date = base::unixtime::now(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags().v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
if (Data::ChatBannedRightsFlags(rights) == 0) {
if (const auto user = participant->asUser()) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
} else {
_additional.setExternal(participant);
}
} else {
const auto kicked = rights.c_chatBannedRights().is_view_messages();
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
const auto kicked = Data::ChatBannedRightsFlags(rights)
& ChatRestriction::f_view_messages;
const auto alreadyRestrictedBy = _additional.restrictedBy(
participant);
_additional.applyParticipant(MTP_channelParticipantBanned(
MTP_flags(kicked
? MTPDchannelParticipantBanned::Flag::f_left
: MTPDchannelParticipantBanned::Flag(0)),
MTP_int(user->bareId()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
MTP_int(date),
rights));
}
if (const auto callback = _bannedDoneCallback) {
callback(user, rights);
callback(participant, rights);
}
}
void AddSpecialBoxController::kickUser(
not_null<UserData*> user,
not_null<PeerData*> participant,
bool sure) {
if (!checkInfoLoaded(user, [=] { kickUser(user); })) {
if (!checkInfoLoaded(participant, [=] { kickUser(participant); })) {
return;
}
const auto kickUserSure = crl::guard(this, [=] {
kickUser(user, true);
kickUser(participant, true);
});
// Check restrictions.
if (_additional.adminRights(user).has_value()
|| _additional.isCreator(user)) {
const auto user = participant->asUser();
if (user && (_additional.adminRights(user).has_value()
|| (_additional.isCreator(user)))) {
// The user is an admin or creator.
if (!_additional.isCreator(user) && _additional.canEditAdmin(user)) {
if (!sure) {
@@ -818,37 +833,39 @@ void AddSpecialBoxController::kickUser(
: tr::lng_profile_sure_kick_channel)(
tr::now,
lt_user,
user->name);
participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(text, kickUserSure),
Ui::LayerOption::KeepOther);
return;
}
const auto restrictedRights = _additional.restrictedRights(user);
const auto restrictedRights = _additional.restrictedRights(participant);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(participant);
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
editRestrictedDone(participant, newRights);
});
const auto fail = crl::guard(this, [=] {
_editBox = nullptr;
});
const auto callback = SaveRestrictedCallback(_peer, user, done, fail);
callback(currentRights, ChannelData::KickedRestrictedRights());
const auto callback = SaveRestrictedCallback(
_peer,
participant,
done,
fail);
callback(currentRights, ChannelData::KickedRestrictedRights(participant));
}
bool AddSpecialBoxController::appendRow(not_null<UserData*> user) {
if (delegate()->peerListFindRow(user->id)
|| (_excludeSelf && user->isSelf())) {
bool AddSpecialBoxController::appendRow(not_null<PeerData*> participant) {
if (delegate()->peerListFindRow(participant->id)
|| (_excludeSelf && participant->isSelf())) {
return false;
}
delegate()->peerListAppendRow(createRow(user));
delegate()->peerListAppendRow(createRow(participant));
return true;
}
@@ -861,8 +878,8 @@ bool AddSpecialBoxController::prependRow(not_null<UserData*> user) {
}
std::unique_ptr<PeerListRow> AddSpecialBoxController::createRow(
not_null<UserData*> user) const {
return std::make_unique<PeerListRow>(user);
not_null<PeerData*> participant) const {
return std::make_unique<PeerListRow>(participant);
}
AddSpecialBoxSearchController::AddSpecialBoxSearchController(

View File

@@ -80,7 +80,7 @@ public:
const MTPChatAdminRights &adminRights,
const QString &rank)>;
using BannedDoneCallback = Fn<void(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &bannedRights)>;
AddSpecialBoxController(
not_null<PeerData*> peer,
@@ -101,7 +101,7 @@ public:
private:
template <typename Callback>
bool checkInfoLoaded(not_null<UserData*> user, Callback callback);
bool checkInfoLoaded(not_null<PeerData*> participant, Callback callback);
void prepareChatRows(not_null<ChatData*> chat);
void rebuildChatRows(not_null<ChatData*> chat);
@@ -113,12 +113,13 @@ private:
const QString &rank);
void showRestricted(not_null<UserData*> user, bool sure = false);
void editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights);
void kickUser(not_null<UserData*> user, bool sure = false);
bool appendRow(not_null<UserData*> user);
void kickUser(not_null<PeerData*> participant, bool sure = false);
bool appendRow(not_null<PeerData*> participant);
bool prependRow(not_null<UserData*> user);
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
std::unique_ptr<PeerListRow> createRow(
not_null<PeerData*> participant) const;
void subscribeToMigration();
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);

View File

@@ -222,7 +222,8 @@ MTPChatAdminRights EditAdminBox::defaultRights() const {
| Flag::f_post_messages
| Flag::f_edit_messages
| Flag::f_delete_messages
| Flag::f_invite_users);
| Flag::f_invite_users
| Flag::f_manage_call);
return MTP_chatAdminRights(MTP_flags(flags));
}
@@ -611,11 +612,11 @@ void EditRestrictedBox::prepare() {
const auto defaultRestrictions = chat
? chat->defaultRestrictions()
: channel->defaultRestrictions();
const auto prepareRights = _oldRights.c_chatBannedRights().vflags().v
const auto prepareRights = Data::ChatBannedRightsFlags(_oldRights)
? _oldRights
: defaultRights();
const auto prepareFlags = FixDependentRestrictions(
prepareRights.c_chatBannedRights().vflags().v
Data::ChatBannedRightsFlags(prepareRights)
| defaultRestrictions
| ((channel && channel->isPublic())
? (Flag::f_change_info | Flag::f_pin_messages)
@@ -646,7 +647,7 @@ void EditRestrictedBox::prepare() {
disabledMessages);
addControl(std::move(checkboxes), QMargins());
_until = prepareRights.c_chatBannedRights().vuntil_date().v;
_until = Data::ChatBannedRightsUntilDate(prepareRights);
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
addControl(
object_ptr<Ui::FlatLabel>(
@@ -766,7 +767,7 @@ void EditRestrictedBox::createUntilVariants() {
}
};
auto addCurrentVariant = [&](TimeId from, TimeId to) {
auto oldUntil = _oldRights.c_chatBannedRights().vuntil_date().v;
auto oldUntil = Data::ChatBannedRightsUntilDate(_oldRights);
if (oldUntil < _until) {
addCustomVariant(oldUntil, from, to);
}

View File

@@ -146,18 +146,18 @@ void SaveChannelAdmin(
void SaveChannelRestriction(
not_null<ChannelData*> channel,
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights,
Fn<void()> onDone,
Fn<void()> onFail) {
channel->session().api().request(MTPchannels_EditBanned(
channel->inputChannel,
user->inputUser,
participant->input,
newRights
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
channel->applyEditBanned(user, oldRights, newRights);
channel->applyEditBanned(participant, oldRights, newRights);
if (onDone) {
onDone();
}
@@ -243,7 +243,7 @@ Fn<void(
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
not_null<PeerData*> peer,
not_null<UserData*> user,
not_null<PeerData*> participant,
Fn<void(const MTPChatBannedRights &newRights)> onDone,
Fn<void()> onFail) {
return [=](
@@ -253,19 +253,21 @@ Fn<void(
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
SaveChannelRestriction(
channel,
user,
participant,
oldRights,
newRights,
done,
onFail);
};
if (const auto chat = peer->asChatNotMigrated()) {
const auto flags = newRights.match([](
const MTPDchatBannedRights &data) {
return data.vflags().v;
});
if (flags & MTPDchatBannedRights::Flag::f_view_messages) {
SaveChatParticipantKick(chat, user, done, onFail);
const auto flags = Data::ChatBannedRightsFlags(newRights);
if (participant->isUser()
&& (flags & MTPDchatBannedRights::Flag::f_view_messages)) {
SaveChatParticipantKick(
chat,
participant->asUser(),
done,
onFail);
} else if (!flags) {
done();
} else {
@@ -313,9 +315,9 @@ ParticipantsAdditionalData::ParticipantsAdditionalData(
}
bool ParticipantsAdditionalData::infoLoaded(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
return _peer->isChat()
|| (_infoNotLoaded.find(user) == end(_infoNotLoaded));
|| (_infoNotLoaded.find(participant) == end(_infoNotLoaded));
}
bool ParticipantsAdditionalData::canEditAdmin(
@@ -342,24 +344,27 @@ bool ParticipantsAdditionalData::canAddOrEditAdmin(
Unexpected("Peer in ParticipantsAdditionalData::canAddOrEditAdmin.");
}
bool ParticipantsAdditionalData::canRestrictUser(
not_null<UserData*> user) const {
if (!canEditAdmin(user) || user->isSelf()) {
bool ParticipantsAdditionalData::canRestrictParticipant(
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (user && (!canEditAdmin(user) || user->isSelf())) {
return false;
} else if (const auto chat = _peer->asChat()) {
return chat->canBanMembers();
} else if (const auto channel = _peer->asChannel()) {
return channel->canBanMembers();
}
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
Unexpected("Peer in ParticipantsAdditionalData::canRestrictParticipant.");
}
bool ParticipantsAdditionalData::canRemoveUser(
not_null<UserData*> user) const {
if (canRestrictUser(user)) {
bool ParticipantsAdditionalData::canRemoveParticipant(
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (canRestrictParticipant(participant)) {
return true;
} else if (const auto chat = _peer->asChat()) {
return !user->isSelf()
return user
&& !user->isSelf()
&& chat->invitedByMe.contains(user)
&& (chat->amCreator() || !_admins.contains(user));
}
@@ -388,12 +393,12 @@ QString ParticipantsAdditionalData::adminRank(
}
auto ParticipantsAdditionalData::restrictedRights(
not_null<UserData*> user) const
not_null<PeerData*> participant) const
-> std::optional<MTPChatBannedRights> {
if (_peer->isChat()) {
return std::nullopt;
}
const auto i = _restrictedRights.find(user);
const auto i = _restrictedRights.find(participant);
return (i != end(_restrictedRights))
? std::make_optional(i->second)
: std::nullopt;
@@ -404,16 +409,18 @@ bool ParticipantsAdditionalData::isCreator(not_null<UserData*> user) const {
}
bool ParticipantsAdditionalData::isExternal(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
return _peer->isChat()
? !_members.contains(user)
: _external.find(user) != end(_external);
? (participant->isUser()
&& !_members.contains(participant->asUser()))
: _external.find(participant) != end(_external);
}
bool ParticipantsAdditionalData::isKicked(not_null<UserData*> user) const {
bool ParticipantsAdditionalData::isKicked(
not_null<PeerData*> participant) const {
return _peer->isChat()
? false
: _kicked.find(user) != end(_kicked);
: _kicked.find(participant) != end(_kicked);
}
UserData *ParticipantsAdditionalData::adminPromotedBy(
@@ -426,29 +433,41 @@ UserData *ParticipantsAdditionalData::adminPromotedBy(
}
UserData *ParticipantsAdditionalData::restrictedBy(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
if (_peer->isChat()) {
return nullptr;
}
const auto i = _restrictedBy.find(user);
const auto i = _restrictedBy.find(participant);
return (i != end(_restrictedBy)) ? i->second.get() : nullptr;
}
void ParticipantsAdditionalData::setExternal(not_null<UserData*> user) {
_infoNotLoaded.erase(user);
_external.emplace(user);
void ParticipantsAdditionalData::setExternal(
not_null<PeerData*> participant) {
if (const auto user = participant->asUser()) {
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
_admins.erase(user);
}
_restrictedRights.erase(participant);
_kicked.erase(participant);
_restrictedBy.erase(participant);
_infoNotLoaded.erase(participant);
_external.emplace(participant);
}
void ParticipantsAdditionalData::checkForLoaded(not_null<UserData*> user) {
void ParticipantsAdditionalData::checkForLoaded(
not_null<PeerData*> participant) {
const auto contains = [](const auto &map, const auto &value) {
return map.find(value) != map.end();
};
if (_creator != user
&& !contains(_adminRights, user)
&& !contains(_restrictedRights, user)
&& !contains(_external, user)
&& !contains(_kicked, user)) {
_infoNotLoaded.emplace(user);
const auto user = participant->asUser();
if (!(user && _creator == user)
&& !(user && contains(_adminRights, user))
&& !contains(_restrictedRights, participant)
&& !contains(_external, participant)
&& !contains(_kicked, participant)) {
_infoNotLoaded.emplace(participant);
}
}
@@ -510,15 +529,15 @@ void ParticipantsAdditionalData::fillFromChannel(
}
}
UserData *ParticipantsAdditionalData::applyParticipant(
PeerData *ParticipantsAdditionalData::applyParticipant(
const MTPChannelParticipant &data) {
return applyParticipant(data, _role);
}
UserData *ParticipantsAdditionalData::applyParticipant(
PeerData *ParticipantsAdditionalData::applyParticipant(
const MTPChannelParticipant &data,
Role overrideRole) {
const auto logBad = [&]() -> UserData* {
const auto logBad = [&]() -> PeerData* {
LOG(("API Error: Bad participant type %1 got "
"while requesting for participants, role: %2"
).arg(data.type()
@@ -526,27 +545,28 @@ UserData *ParticipantsAdditionalData::applyParticipant(
return nullptr;
};
return data.match([&](const MTPDchannelParticipantCreator &data) {
return data.match([&](
const MTPDchannelParticipantCreator &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members
&& overrideRole != Role::Admins) {
return logBad();
}
return applyCreator(data);
}, [&](const MTPDchannelParticipantAdmin &data) {
}, [&](const MTPDchannelParticipantAdmin &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members
&& overrideRole != Role::Admins) {
return logBad();
}
return applyAdmin(data);
}, [&](const MTPDchannelParticipantSelf &data) {
}, [&](const MTPDchannelParticipantSelf &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members) {
return logBad();
}
return applyRegular(data.vuser_id());
}, [&](const MTPDchannelParticipant &data) {
}, [&](const MTPDchannelParticipant &data) -> PeerData* {
if (overrideRole != Role::Profile
&& overrideRole != Role::Members) {
return logBad();
@@ -645,32 +665,35 @@ UserData *ParticipantsAdditionalData::applyRegular(MTPint userId) {
return user;
}
UserData *ParticipantsAdditionalData::applyBanned(
PeerData *ParticipantsAdditionalData::applyBanned(
const MTPDchannelParticipantBanned &data) {
const auto user = _peer->owner().userLoaded(data.vuser_id().v);
if (!user) {
const auto participant = _peer->owner().peerLoaded(
peerFromMTP(data.vpeer()));
if (!participant) {
return nullptr;
}
_infoNotLoaded.erase(user);
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
if (data.is_left()) {
_kicked.emplace(user);
} else {
_kicked.erase(user);
_infoNotLoaded.erase(participant);
if (const auto user = participant->asUser()) {
_adminRights.erase(user);
_adminCanEdit.erase(user);
_adminPromotedBy.erase(user);
}
_restrictedRights[user] = data.vbanned_rights();
if (data.is_left()) {
_kicked.emplace(participant);
} else {
_kicked.erase(participant);
}
_restrictedRights[participant] = data.vbanned_rights();
if (const auto by = _peer->owner().userLoaded(data.vkicked_by().v)) {
const auto i = _restrictedBy.find(user);
const auto i = _restrictedBy.find(participant);
if (i == _restrictedBy.end()) {
_restrictedBy.emplace(user, by);
_restrictedBy.emplace(participant, by);
} else {
i->second = by;
}
}
return user;
return participant;
}
void ParticipantsAdditionalData::migrate(
@@ -922,9 +945,9 @@ void ParticipantsBoxController::addNewItem() {
editAdminDone(user, rights, rank);
});
const auto restrictedDone = crl::guard(this, [=](
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
editRestrictedDone(user, rights);
editRestrictedDone(participant, rights);
});
const auto initBox = [](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
@@ -955,8 +978,10 @@ void ParticipantsBoxController::addNewParticipants() {
auto already = std::vector<not_null<UserData*>>();
already.reserve(count);
for (auto i = 0; i != count; ++i) {
already.emplace_back(
delegate()->peerListRowAt(i)->peer()->asUser());
const auto participant = delegate()->peerListRowAt(i)->peer();
if (const auto user = participant->asUser()) {
already.emplace_back(user);
}
}
AddParticipantsBoxController::Start(
_navigation,
@@ -1165,6 +1190,7 @@ void ParticipantsBoxController::rebuildChatParticipants(
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
Assert(row->peer()->isUser());
auto user = row->peer()->asUser();
if (participants.contains(user)) {
++i;
@@ -1316,8 +1342,9 @@ void ParticipantsBoxController::loadMoreRows() {
int availableCount,
const QVector<MTPChannelParticipant> &list) {
for (const auto &data : list) {
if (const auto user = _additional.applyParticipant(data)) {
appendRow(user);
if (const auto participant = _additional.applyParticipant(
data)) {
appendRow(participant);
}
}
if (const auto size = list.size()) {
@@ -1398,31 +1425,32 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
}
void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Admins) {
Assert(user != nullptr);
showAdmin(user);
} else if (_role == Role::Restricted
&& (_peer->isChat() || _peer->isMegagroup())) {
&& (_peer->isChat() || _peer->isMegagroup())
&& user) {
showRestricted(user);
} else {
Assert(_navigation != nullptr);
_navigation->showPeerInfo(user);
_navigation->showPeerInfo(participant);
}
}
void ParticipantsBoxController::rowActionClicked(
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Members || _role == Role::Profile) {
kickMember(user);
kickParticipant(participant);
} else if (_role == Role::Admins) {
Assert(user != nullptr);
removeAdmin(user);
} else {
removeKicked(row, user);
removeKicked(row, participant);
}
}
@@ -1433,28 +1461,30 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
if (_navigation) {
result->addAction(
tr::lng_context_view_profile(tr::now),
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
crl::guard(this, [=] {
_navigation->showPeerInfo(participant); }));
}
if (_role == Role::Kicked) {
if (_peer->isMegagroup()
&& _additional.canRestrictUser(user)) {
if (channel->canAddMembers()) {
&& _additional.canRestrictParticipant(participant)) {
if (user && channel->canAddMembers()) {
result->addAction(
tr::lng_context_add_to_group(tr::now),
crl::guard(this, [=] { unkickMember(user); }));
crl::guard(this, [=] { unkickParticipant(user); }));
}
result->addAction(
tr::lng_profile_delete_removed(tr::now),
crl::guard(this, [=] { removeKickedWithRow(user); }));
crl::guard(this, [=] { removeKickedWithRow(participant); }));
}
return result;
}
if (_additional.canAddOrEditAdmin(user)) {
if (user && _additional.canAddOrEditAdmin(user)) {
const auto isAdmin = _additional.isCreator(user)
|| _additional.adminRights(user).has_value();
result->addAction(
@@ -1463,7 +1493,7 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
: tr::lng_context_promote_admin)(tr::now),
crl::guard(this, [=] { showAdmin(user); }));
}
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(participant)) {
const auto canRestrictWithoutKick = [&] {
if (const auto chat = _peer->asChat()) {
return chat->amCreator();
@@ -1476,14 +1506,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
crl::guard(this, [=] { showRestricted(user); }));
}
}
if (_additional.canRemoveUser(user)) {
if (!_additional.isKicked(user)) {
if (_additional.canRemoveParticipant(participant)) {
if (!_additional.isKicked(participant)) {
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
result->addAction(
(isGroup
? tr::lng_context_remove_from_group
: tr::lng_profile_kick)(tr::now),
crl::guard(this, [=] { kickMember(user); }));
crl::guard(this, [=] { kickParticipant(user); }));
}
}
return result;
@@ -1569,9 +1599,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
const auto restrictedRights = _additional.restrictedRights(user);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(user);
const auto hasAdminRights = _additional.adminRights(user).has_value();
auto box = Box<EditRestrictedBox>(
_peer,
@@ -1580,7 +1608,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
currentRights);
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(user)) {
const auto done = crl::guard(this, [=](
const MTPChatBannedRights &newRights) {
editRestrictedDone(user, newRights);
@@ -1597,72 +1625,84 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
}
void ParticipantsBoxController::editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights) {
_addBox = nullptr;
if (_editParticipantBox) {
_editParticipantBox->closeBox();
}
const auto user = participant->asUser();
const auto date = base::unixtime::now(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags().v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
if (Data::ChatBannedRightsFlags(rights) == 0) {
if (user) {
_additional.applyParticipant(MTP_channelParticipant(
MTP_int(user->bareId()),
MTP_int(date)));
} else {
_additional.setExternal(participant);
}
if (_role == Role::Kicked || _role == Role::Restricted) {
removeRow(user);
removeRow(participant);
}
} else {
const auto kicked = rights.c_chatBannedRights().is_view_messages();
const auto alreadyRestrictedBy = _additional.restrictedBy(user);
const auto kicked = Data::ChatBannedRightsFlags(rights)
& ChatRestriction::f_view_messages;
const auto alreadyRestrictedBy = _additional.restrictedBy(
participant);
_additional.applyParticipant(MTP_channelParticipantBanned(
MTP_flags(kicked
? MTPDchannelParticipantBanned::Flag::f_left
: MTPDchannelParticipantBanned::Flag(0)),
MTP_int(user->bareId()),
(participant->isUser()
? MTP_peerUser(MTP_int(participant->bareId()))
: participant->isChat()
? MTP_peerChat(MTP_int(participant->bareId()))
: MTP_peerChannel(MTP_int(participant->bareId()))),
MTP_int(alreadyRestrictedBy
? alreadyRestrictedBy->bareId()
: user->session().userId()),
: participant->session().userId()),
MTP_int(date),
rights));
if (kicked) {
if (_role == Role::Kicked) {
prependRow(user);
prependRow(participant);
} else if (_role == Role::Admins
|| _role == Role::Restricted
|| _role == Role::Members) {
removeRow(user);
removeRow(participant);
}
} else {
if (_role == Role::Restricted) {
prependRow(user);
prependRow(participant);
} else if (_role == Role::Kicked
|| _role == Role::Admins
|| _role == Role::Members) {
removeRow(user);
removeRow(participant);
}
}
}
recomputeTypeFor(user);
recomputeTypeFor(participant);
delegate()->peerListRefreshRows();
}
void ParticipantsBoxController::kickMember(not_null<UserData*> user) {
void ParticipantsBoxController::kickParticipant(not_null<PeerData*> participant) {
const auto user = participant->asUser();
const auto text = ((_peer->isChat() || _peer->isMegagroup())
? tr::lng_profile_sure_kick
: tr::lng_profile_sure_kick_channel)(
tr::now,
lt_user,
user->firstName);
user ? user->firstName : participant->name);
_editBox = Ui::show(
Box<ConfirmBox>(
text,
tr::lng_box_remove(tr::now),
crl::guard(this, [=] { kickMemberSure(user); })),
crl::guard(this, [=] { kickParticipantSure(participant); })),
Ui::LayerOption::KeepOther);
}
void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
void ParticipantsBoxController::unkickParticipant(not_null<UserData*> user) {
_editBox = nullptr;
if (const auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListRemoveRow(row);
@@ -1671,25 +1711,24 @@ void ParticipantsBoxController::unkickMember(not_null<UserData*> user) {
_peer->session().api().addChatParticipants(_peer, { 1, user });
}
void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) {
void ParticipantsBoxController::kickParticipantSure(
not_null<PeerData*> participant) {
_editBox = nullptr;
const auto restrictedRights = _additional.restrictedRights(user);
const auto restrictedRights = _additional.restrictedRights(participant);
const auto currentRights = restrictedRights
? *restrictedRights
: MTPChatBannedRights(MTP_chatBannedRights(
MTP_flags(0),
MTP_int(0)));
: ChannelData::EmptyRestrictedRights(participant);
if (const auto row = delegate()->peerListFindRow(user->id)) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
delegate()->peerListRemoveRow(row);
delegate()->peerListRefreshRows();
}
auto &session = _peer->session();
if (const auto chat = _peer->asChat()) {
session.api().kickParticipant(chat, user);
session.api().kickParticipant(chat, participant);
} else if (const auto channel = _peer->asChannel()) {
session.api().kickParticipant(channel, user, currentRights);
session.api().kickParticipant(channel, participant, currentRights);
}
}
@@ -1730,36 +1769,37 @@ void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) {
}
void ParticipantsBoxController::removeKickedWithRow(
not_null<UserData*> user) {
if (const auto row = delegate()->peerListFindRow(user->id)) {
removeKicked(row, user);
not_null<PeerData*> participant) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
removeKicked(row, participant);
} else {
removeKicked(user);
removeKicked(participant);
}
}
void ParticipantsBoxController::removeKicked(not_null<UserData*> user) {
void ParticipantsBoxController::removeKicked(
not_null<PeerData*> participant) {
if (const auto channel = _peer->asChannel()) {
channel->session().api().unblockParticipant(channel, user);
channel->session().api().unblockParticipant(channel, participant);
}
}
void ParticipantsBoxController::removeKicked(
not_null<PeerListRow*> row,
not_null<UserData*> user) {
not_null<PeerData*> participant) {
delegate()->peerListRemoveRow(row);
if (_role != Role::Kicked
&& !delegate()->peerListFullRowsCount()) {
setDescriptionText(tr::lng_blocked_list_not_found(tr::now));
}
delegate()->peerListRefreshRows();
removeKicked(user);
removeKicked(participant);
}
bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
if (delegate()->peerListFindRow(user->id)) {
recomputeTypeFor(user);
bool ParticipantsBoxController::appendRow(not_null<PeerData*> participant) {
if (delegate()->peerListFindRow(participant->id)) {
recomputeTypeFor(participant);
return false;
} else if (auto row = createRow(user)) {
} else if (auto row = createRow(participant)) {
delegate()->peerListAppendRow(std::move(row));
if (_role != Role::Kicked) {
setDescriptionText(QString());
@@ -1769,16 +1809,16 @@ bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
return false;
}
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
if (const auto row = delegate()->peerListFindRow(user->id)) {
recomputeTypeFor(user);
bool ParticipantsBoxController::prependRow(not_null<PeerData*> participant) {
if (const auto row = delegate()->peerListFindRow(participant->id)) {
recomputeTypeFor(participant);
refreshCustomStatus(row);
if (_role == Role::Admins) {
// Perhaps we've added a new admin from search.
delegate()->peerListPrependRowFromSearchResult(row);
}
return false;
} else if (auto row = createRow(user)) {
} else if (auto row = createRow(participant)) {
delegate()->peerListPrependRow(std::move(row));
if (_role != Role::Kicked) {
setDescriptionText(QString());
@@ -1788,8 +1828,8 @@ bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
return false;
}
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id)) {
bool ParticipantsBoxController::removeRow(not_null<PeerData*> participant) {
if (auto row = delegate()->peerListFindRow(participant->id)) {
if (_role == Role::Admins) {
// Perhaps we are removing an admin from search results.
row->setCustomStatus(tr::lng_channel_admin_status_not_admin(tr::now));
@@ -1807,24 +1847,28 @@ bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
}
std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
not_null<UserData*> user) const {
not_null<PeerData*> participant) const {
const auto user = participant->asUser();
if (_role == Role::Profile) {
Assert(user != nullptr);
return std::make_unique<Row>(user, computeType(user));
}
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
auto row = std::make_unique<PeerListRowWithLink>(user);
auto row = std::make_unique<PeerListRowWithLink>(participant);
refreshCustomStatus(row.get());
if (_role == Role::Admins
&& user
&& !_additional.isCreator(user)
&& _additional.adminRights(user).has_value()
&& _additional.canEditAdmin(user)) {
row->setActionLink(tr::lng_profile_kick(tr::now));
} else if (_role == Role::Kicked || _role == Role::Restricted) {
if (_additional.canRestrictUser(user)) {
if (_additional.canRestrictParticipant(participant)) {
row->setActionLink(tr::lng_profile_delete_removed(tr::now));
}
} else if (_role == Role::Members) {
Assert(user != nullptr);
if ((chat ? chat->canBanMembers() : channel->canBanMembers())
&& !_additional.isCreator(user)
&& (!_additional.adminRights(user)
@@ -1842,31 +1886,34 @@ std::unique_ptr<PeerListRow> ParticipantsBoxController::createRow(
}
auto ParticipantsBoxController::computeType(
not_null<UserData*> user) const -> Type {
not_null<PeerData*> participant) const -> Type {
const auto user = participant->asUser();
auto result = Type();
result.rights = _additional.isCreator(user)
result.rights = (user && _additional.isCreator(user))
? Rights::Creator
: _additional.adminRights(user).has_value()
: (user && _additional.adminRights(user).has_value())
? Rights::Admin
: Rights::Normal;
result.canRemove = _additional.canRemoveUser(user);
result.canRemove = _additional.canRemoveParticipant(participant);
return result;
}
void ParticipantsBoxController::recomputeTypeFor(
not_null<UserData*> user) {
not_null<PeerData*> participant) {
if (_role != Role::Profile) {
return;
}
if (const auto row = delegate()->peerListFindRow(user->id)) {
static_cast<Row*>(row)->setType(computeType(user));
if (const auto row = delegate()->peerListFindRow(participant->id)) {
static_cast<Row*>(row)->setType(computeType(participant));
}
}
void ParticipantsBoxController::refreshCustomStatus(
not_null<PeerListRow*> row) const {
const auto user = row->peer()->asUser();
const auto participant = row->peer();
const auto user = participant->asUser();
if (_role == Role::Admins) {
Assert(user != nullptr);
if (const auto by = _additional.adminPromotedBy(user)) {
row->setCustomStatus(tr::lng_channel_admin_status_promoted_by(
tr::now,
@@ -1882,7 +1929,7 @@ void ParticipantsBoxController::refreshCustomStatus(
}
}
} else if (_role == Role::Kicked || _role == Role::Restricted) {
const auto by = _additional.restrictedBy(user);
const auto by = _additional.restrictedBy(participant);
row->setCustomStatus((_role == Role::Kicked
? tr::lng_channel_banned_status_removed_by
: tr::lng_channel_banned_status_restricted_by)(

View File

@@ -33,7 +33,7 @@ Fn<void(
const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights)> SaveRestrictedCallback(
not_null<PeerData*> peer,
not_null<UserData*> user,
not_null<PeerData*> participant,
Fn<void(const MTPChatBannedRights &newRights)> onDone,
Fn<void()> onFail);
@@ -77,29 +77,31 @@ public:
ParticipantsAdditionalData(not_null<PeerData*> peer, Role role);
UserData *applyParticipant(const MTPChannelParticipant &data);
UserData *applyParticipant(
PeerData *applyParticipant(const MTPChannelParticipant &data);
PeerData *applyParticipant(
const MTPChannelParticipant &data,
Role overrideRole);
void setExternal(not_null<UserData*> user);
void checkForLoaded(not_null<UserData*> user);
void setExternal(not_null<PeerData*> participant);
void checkForLoaded(not_null<PeerData*> participant);
void fillFromPeer();
[[nodiscard]] bool infoLoaded(not_null<UserData*> user) const;
[[nodiscard]] bool infoLoaded(not_null<PeerData*> participant) const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant(
not_null<PeerData*> participant) const;
[[nodiscard]] bool canRemoveParticipant(
not_null<PeerData*> participant) const;
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
not_null<UserData*> user) const;
QString adminRank(not_null<UserData*> user) const;
[[nodiscard]] std::optional<MTPChatBannedRights> restrictedRights(
not_null<UserData*> user) const;
not_null<PeerData*> participant) const;
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<UserData*> user) const;
[[nodiscard]] bool isKicked(not_null<UserData*> user) const;
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
@@ -107,7 +109,7 @@ private:
UserData *applyCreator(const MTPDchannelParticipantCreator &data);
UserData *applyAdmin(const MTPDchannelParticipantAdmin &data);
UserData *applyRegular(MTPint userId);
UserData *applyBanned(const MTPDchannelParticipantBanned &data);
PeerData *applyBanned(const MTPDchannelParticipantBanned &data);
void fillFromChat(not_null<ChatData*> chat);
void fillFromChannel(not_null<ChannelData*> channel);
@@ -124,11 +126,11 @@ private:
base::flat_map<not_null<UserData*>, QString> _adminRanks;
base::flat_set<not_null<UserData*>> _adminCanEdit;
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
std::map<not_null<UserData*>, MTPChatBannedRights> _restrictedRights;
std::set<not_null<UserData*>> _kicked;
std::map<not_null<UserData*>, not_null<UserData*>> _restrictedBy;
std::set<not_null<UserData*>> _external;
std::set<not_null<UserData*>> _infoNotLoaded;
std::map<not_null<PeerData*>, MTPChatBannedRights> _restrictedRights;
std::set<not_null<PeerData*>> _kicked;
std::map<not_null<PeerData*>, not_null<UserData*>> _restrictedBy;
std::set<not_null<PeerData*>> _external;
std::set<not_null<PeerData*>> _infoNotLoaded;
};
@@ -181,7 +183,7 @@ protected:
Role role);
virtual std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const;
not_null<PeerData*> participant) const;
private:
using Row = Info::Profile::MemberListRow;
@@ -223,23 +225,25 @@ private:
const QString &rank);
void showRestricted(not_null<UserData*> user);
void editRestrictedDone(
not_null<UserData*> user,
not_null<PeerData*> participant,
const MTPChatBannedRights &rights);
void removeKicked(not_null<PeerListRow*> row, not_null<UserData*> user);
void removeKickedWithRow(not_null<UserData*> user);
void removeKicked(not_null<UserData*> user);
void kickMember(not_null<UserData*> user);
void kickMemberSure(not_null<UserData*> user);
void unkickMember(not_null<UserData*> user);
void removeKicked(
not_null<PeerListRow*> row,
not_null<PeerData*> participant);
void removeKickedWithRow(not_null<PeerData*> participant);
void removeKicked(not_null<PeerData*> participant);
void kickParticipant(not_null<PeerData*> participant);
void kickParticipantSure(not_null<PeerData*> participant);
void unkickParticipant(not_null<UserData*> user);
void removeAdmin(not_null<UserData*> user);
void removeAdminSure(not_null<UserData*> user);
bool appendRow(not_null<UserData*> user);
bool prependRow(not_null<UserData*> user);
bool removeRow(not_null<UserData*> user);
bool appendRow(not_null<PeerData*> participant);
bool prependRow(not_null<PeerData*> participant);
bool removeRow(not_null<PeerData*> participant);
void refreshCustomStatus(not_null<PeerListRow*> row) const;
bool feedMegagroupLastParticipants();
Type computeType(not_null<UserData*> user) const;
void recomputeTypeFor(not_null<UserData*> user);
Type computeType(not_null<PeerData*> participant) const;
void recomputeTypeFor(not_null<PeerData*> participant);
void subscribeToMigration();
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);

View File

@@ -151,6 +151,7 @@ std::vector<std::pair<ChatAdminRights, QString>> AdminRightLabels(
{ Flag::f_edit_messages, tr::lng_rights_channel_edit(tr::now) },
{ Flag::f_delete_messages, tr::lng_rights_channel_delete(tr::now) },
{ Flag::f_invite_users, tr::lng_rights_group_invite(tr::now) },
{ Flag::f_manage_call, tr::lng_rights_group_manage_calls(tr::now) },
{ Flag::f_add_admins, tr::lng_rights_add_admins(tr::now) }
};
}

View File

@@ -566,7 +566,6 @@ int32 StickerSetBox::Inner::stickerFromGlobalPos(const QPoint &p) const {
}
void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
QRect r(e->rect());
Painter p(this);
if (_elements.empty()) {

View File

@@ -792,9 +792,10 @@ groupCallBoxLabel: FlatLabel(boxLabel) {
textFg: groupCallMembersFg;
}
groupCallJoinAsLabel: FlatLabel(defaultFlatLabel) {
minWidth: 282px;
minWidth: 272px;
textFg: groupCallMembersFg;
}
groupCallJoinAsWidth: 330px;
groupCallJoinAsTextTop: 4px;
groupCallJoinAsNameTop: 23px;
groupCallJoinAsPadding: margins(12px, 8px, 12px, 7px);

View File

@@ -142,7 +142,7 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
}
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
return WrapVersions(tgcalls::Meta::Versions() | ranges::action::reverse);
return WrapVersions(tgcalls::Meta::Versions() | ranges::actions::reverse);
}
[[nodiscard]] Webrtc::VideoState StartVideoState(bool enabled) {

View File

@@ -12,12 +12,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_group_call.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "boxes/peer_list_box.h"
#include "boxes/confirm_box.h"
#include "styles/style_boxes.h"
#include "styles/style_calls.h"
@@ -111,10 +114,12 @@ void ChooseJoinAsBox(
Context context,
JoinInfo info,
Fn<void(JoinInfo)> done) {
box->setWidth(st::groupCallJoinAsWidth);
box->setTitle([&] {
switch (context) {
case Context::Create: return tr::lng_group_call_start_as_header();
case Context::Join: return tr::lng_group_call_join_as_header();
case Context::Join:
case Context::JoinWithConfirm: return tr::lng_group_call_join_as_header();
case Context::Switch: return tr::lng_group_call_display_as_header();
}
Unexpected("Context in ChooseJoinAsBox.");
@@ -158,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() {
@@ -172,7 +208,7 @@ void ChooseJoinAsProcess::start(
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
Fn<void(QString)> showToast,
Fn<void(JoinInfo)> done,
PeerData *currentJoinAs) {
PeerData *changingJoinAsFrom) {
Expects(done != nullptr);
const auto session = &peer->session();
@@ -231,26 +267,18 @@ void ChooseJoinAsProcess::start(
}
return list;
});
const auto selectedId = peer->groupCallDefaultJoinAs();
if (list.empty()) {
_request->showToast(Lang::Hard::ServerError());
return;
} else if (list.size() == 1
&& list.front() == self
&& (!peer->isChannel()
|| !peer->asChannel()->amAnonymous()
|| (peer->isBroadcast() && !peer->canWrite()))) {
info.possibleJoinAs = std::move(list);
finish(info);
return;
}
info.joinAs = [&]() -> not_null<PeerData*> {
const auto selectedId = peer->groupCallDefaultJoinAs();
if (!selectedId) {
return self;
}
const auto loaded = session->data().peerLoaded(selectedId);
return (currentJoinAs && ranges::contains(list, not_null{ currentJoinAs }))
? not_null(currentJoinAs)
const auto loaded = selectedId
? session->data().peerLoaded(selectedId)
: nullptr;
return (changingJoinAsFrom
&& ranges::contains(list, not_null{ changingJoinAsFrom }))
? not_null(changingJoinAsFrom)
: (loaded && ranges::contains(list, not_null{ loaded }))
? not_null(loaded)
: ranges::contains(list, self)
@@ -259,6 +287,38 @@ void ChooseJoinAsProcess::start(
}();
info.possibleJoinAs = std::move(list);
const auto onlyByMe = (info.possibleJoinAs.size() == 1)
&& (info.possibleJoinAs.front() == self);
// We already joined this voice chat, just rejoin with the same.
const auto byAlreadyUsed = selectedId
&& (info.joinAs->id == selectedId)
&& (peer->groupCall() != nullptr);
if (!changingJoinAsFrom && (onlyByMe || byAlreadyUsed)) {
const auto confirmation = CreateOrJoinConfirmation(
peer,
context,
byAlreadyUsed);
if (confirmation.text.isEmpty()) {
finish(info);
return;
}
auto box = Box<::ConfirmBox>(
confirmation,
(peer->groupCall()
? tr::lng_group_call_join(tr::now)
: tr::lng_create_group_create(tr::now)),
crl::guard(&_request->guard, [=] { finish(info); }));
box->boxClosing(
) | rpl::start_with_next([=] {
_request = nullptr;
}, _request->lifetime);
_request->box = box.data();
_request->showBox(std::move(box));
return;
}
auto box = Box(
ChooseJoinAsBox,
context,

View File

@@ -28,6 +28,7 @@ public:
enum class Context {
Create,
Join,
JoinWithConfirm,
Switch,
};
@@ -37,7 +38,7 @@ public:
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
Fn<void(QString)> showToast,
Fn<void(JoinInfo)> done,
PeerData *currentJoinAs = nullptr);
PeerData *changingJoinAsFrom = nullptr);
private:
struct ChannelsListRequest {

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_common.h"
#include "main/main_session.h"
#include "api/api_send_progress.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "lang/lang_hardcoded.h"
@@ -272,11 +273,6 @@ void GroupCall::setState(State state) {
if (state == State::Joined) {
stopConnectingSound();
if (!_hadJoinedState) {
_hadJoinedState = true;
applyGlobalShortcutChanges();
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
}
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
call->setInCall();
}
@@ -392,8 +388,11 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
addParticipantsToInstance();
_peer->session().updates().addActiveChat(
_peerStream.events_starting_with_copy(_peer));
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
_peer = group;
_peerStream.fire_copy(group);
});
}
@@ -426,6 +425,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
LOG(("Call Info: Requesting join payload."));
_joinAs = as;
if (const auto chat = _peer->asChat()) {
chat->setGroupCallDefaultJoinAs(_joinAs->id);
} else if (const auto channel = _peer->asChannel()) {
channel->setGroupCallDefaultJoinAs(_joinAs->id);
}
const auto weak = base::make_weak(this);
_instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) {
@@ -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
@@ -474,6 +479,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
applyMeInCallLocally();
maybeSendMutedUpdate(wasMuteState);
_peer->session().api().applyUpdates(updates);
checkFirstTimeJoined();
}).fail([=](const MTP::Error &error) {
const auto type = error.type();
LOG(("Call Error: Could not join, error: %1").arg(type));
@@ -542,7 +548,7 @@ void GroupCall::applyMeInCallLocally() {
| Flag::f_volume_by_admin // Self volume can only be set by admin.
| ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0))
| (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0));
call->applyUpdateChecked(
call->applyLocalUpdate(
MTP_updateGroupCallParticipants(
inputCall(),
MTP_vector<MTPGroupCallParticipant>(
@@ -587,7 +593,7 @@ void GroupCall::applyParticipantLocally(
| (participant->raisedHandRating
? Flag::f_raise_hand_rating
: Flag(0));
_peer->groupCall()->applyUpdateChecked(
_peer->groupCall()->applyLocalUpdate(
MTP_updateGroupCallParticipants(
inputCall(),
MTP_vector<MTPGroupCallParticipant>(
@@ -712,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
@@ -848,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;
@@ -885,18 +925,31 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
handleOtherParticipants(data);
return;
}
if (data.is_left() && data.vsource().v == _mySsrc) {
// I was removed from the call, rejoin.
LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
setState(State::Joining);
rejoin();
} else if (!data.is_left() && 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 (data.is_left()) {
if (data.vsource().v == _mySsrc) {
// I was removed from the call, rejoin.
LOG(("Call Info: "
"Rejoin after got 'left' with my ssrc."));
setState(State::Joining);
rejoin();
}
return;
} else if (data.vsource().v != _mySsrc) {
if (!_mySsrcs.contains(data.vsource().v)) {
// I joined from another device, hangup.
LOG(("Call Info: "
"Hangup after '!left' with ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
_mySsrc = 0;
hangup();
} else {
LOG(("Call Info: "
"Some old 'self' with '!left' and ssrc %1, my %2."
).arg(data.vsource().v
).arg(_mySsrc));
}
return;
}
if (data.is_muted() && !data.is_can_self_unmute()) {
setMuted(data.vraise_hand_rating().value_or_empty()
@@ -925,12 +978,12 @@ void GroupCall::changeTitle(const QString &title) {
return;
}
real->setTitle(title);
_api.request(MTPphone_EditGroupCallTitle(
inputCall(),
MTP_string(title)
)).done([=](const MTPUpdates &result) {
_peer->session().api().applyUpdates(result);
_titleChanged.fire({});
}).fail([=](const MTP::Error &error) {
}).send();
}
@@ -1082,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();
}
@@ -1090,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({
@@ -1303,9 +1358,24 @@ void GroupCall::setInstanceConnected(
if (nowCanSpeak) {
notifyAboutAllowedToSpeak();
}
if (!_hadJoinedState && state() == State::Joined) {
checkFirstTimeJoined();
}
}
void GroupCall::checkFirstTimeJoined() {
if (_hadJoinedState || state() != State::Joined) {
return;
}
_hadJoinedState = true;
applyGlobalShortcutChanges();
_delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
}
void GroupCall::notifyAboutAllowedToSpeak() {
if (!_hadJoinedState) {
return;
}
_delegate->groupCallPlaySound(
Delegate::GroupCallSound::AllowedToSpeak);
_allowedToSpeakNotifications.fire({});
@@ -1369,11 +1439,6 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) {
}).send();
}
auto GroupCall::instanceStateValue() const -> rpl::producer<InstanceState> {
using namespace rpl::mappers;
return _instanceState.value();
}
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
if (input) {
_mediaDevices->switchToAudioInput(deviceId);

View File

@@ -116,8 +116,8 @@ public:
void rejoinAs(Group::JoinInfo info);
void rejoinWithHash(const QString &hash);
void join(const MTPInputGroupCall &inputCall);
void handleUpdate(const MTPGroupCall &call);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleUpdate(const MTPUpdate &update);
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void changeTitle(const QString &title);
void toggleRecording(bool enabled, const QString &title);
[[nodiscard]] bool recordingStoppedByMe() const {
@@ -158,7 +158,12 @@ public:
TransitionToRtc,
Connected,
};
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const;
[[nodiscard]] InstanceState instanceState() const {
return _instanceState.current();
}
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const {
return _instanceState.value();
}
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
return _levelUpdates.events();
@@ -169,6 +174,9 @@ public:
[[nodiscard]] rpl::producer<> allowedToSpeakNotifications() const {
return _allowedToSpeakNotifications.events();
}
[[nodiscard]] rpl::producer<> titleChanged() const {
return _titleChanged.events();
}
static constexpr auto kSpeakLevelThreshold = 0.2;
void setCurrentAudioDevice(bool input, const QString &deviceId);
@@ -219,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();
@@ -242,6 +254,7 @@ private:
void checkGlobalShortcutAvailability();
void checkJoined();
void checkFirstTimeJoined();
void notifyAboutAllowedToSpeak();
void playConnectingSound();
@@ -268,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;
@@ -296,6 +310,7 @@ private:
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _updateMuteRequestId = 0;
@@ -304,6 +319,7 @@ private:
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
rpl::event_stream<> _allowedToSpeakNotifications;
rpl::event_stream<> _titleChanged;
base::Timer _lastSpokeCheckTimer;
base::Timer _checkJoinedTimer;

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_call.h"
#include "calls/calls_group_common.h"
#include "calls/calls_group_menu.h"
#include "calls/calls_volume_item.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@@ -37,7 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_calls.h"
namespace Calls {
namespace Calls::Group {
namespace {
constexpr auto kBlobsEnterDuration = crl::time(250);
@@ -47,6 +48,7 @@ constexpr auto kMinorBlobFactor = 0.9f;
constexpr auto kUserpicMinScale = 0.8;
constexpr auto kMaxLevel = 1.;
constexpr auto kWideScale = 5;
constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
const auto kSpeakerThreshold = std::vector<float>{
Group::kDefaultVolume * 0.1f / Group::kMaxVolume,
@@ -89,6 +91,7 @@ public:
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
virtual bool rowCanMuteMembers() = 0;
virtual void rowUpdateRow(not_null<Row*> row) = 0;
virtual void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) = 0;
virtual void rowPaintIcon(
Painter &p,
QRect rect,
@@ -115,6 +118,7 @@ public:
void updateState(const Data::GroupCall::Participant *participant);
void updateLevel(float level);
void updateBlobAnimation(crl::time now);
void clearRaisedHandStatus();
[[nodiscard]] State state() const {
return _state;
}
@@ -140,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,
@@ -254,6 +255,7 @@ private:
int _volume = Group::kDefaultVolume;
bool _sounding = false;
bool _speaking = false;
bool _raisedHandStatus = false;
bool _skipLevelUpdate = false;
};
@@ -291,6 +293,7 @@ public:
bool rowIsMe(not_null<PeerData*> participantPeer) override;
bool rowCanMuteMembers() override;
void rowUpdateRow(not_null<Row*> row) override;
void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) override;
void rowPaintIcon(
Painter &p,
QRect rect,
@@ -335,6 +338,7 @@ private:
[[nodiscard]] Data::GroupCall *resolvedRealCall() const;
void appendInvitedUsers();
void scheduleRaisedHandStatusRemove();
const base::weak_ptr<GroupCall> _call;
not_null<PeerData*> _peer;
@@ -348,13 +352,14 @@ private:
rpl::event_stream<VolumeRequest> _changeVolumeRequests;
rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
rpl::variable<int> _fullCount = 1;
rpl::variable<int> _fullCountMin = 0;
rpl::variable<int> _fullCountMax = std::numeric_limits<int>::max();
not_null<QWidget*> _menuParent;
base::unique_qptr<Ui::PopupMenu> _menu;
base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
base::flat_map<PeerListRowId, crl::time> _raisedHandStatusRemoveAt;
base::Timer _raisedHandStatusRemoveTimer;
base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
Ui::Animations::Basic _soundingAnimation;
@@ -479,6 +484,15 @@ void Row::setSounding(bool sounding) {
}
}
void Row::clearRaisedHandStatus() {
if (!_raisedHandStatus) {
return;
}
_raisedHandStatus = false;
refreshStatus();
_delegate->rowUpdateRow(this);
}
void Row::setState(State state) {
if (_state == state) {
return;
@@ -486,10 +500,16 @@ void Row::setState(State state) {
const auto wasActive = (_state == State::Active);
const auto wasMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto wasRaisedHand = (_state == State::RaisedHand);
_state = state;
const auto nowActive = (_state == State::Active);
const auto nowMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
const auto nowRaisedHand = (_state == State::RaisedHand);
if (!wasRaisedHand && nowRaisedHand) {
_raisedHandStatus = true;
_delegate->rowScheduleRaisedHandStatusRemove(this);
}
if (nowActive != wasActive) {
_activeAnimation.start(
[=] { _delegate->rowUpdateRow(this); },
@@ -710,10 +730,10 @@ void Row::paintStatusText(
const auto &font = st::normalFont;
const auto about = (_state == State::Inactive
|| _state == State::Muted
|| _state == State::RaisedHand)
|| (_state == State::RaisedHand && !_raisedHandStatus))
? _aboutText
: QString();
if (_aboutText.isEmpty()
if (about.isEmpty()
&& _state != State::Invited
&& _state != State::MutedByMe) {
p.save();
@@ -743,8 +763,8 @@ void Row::paintStatusText(
outerWidth,
(_state == State::MutedByMe
? tr::lng_group_call_muted_by_me_status(tr::now)
: !_aboutText.isEmpty()
? font->m.elidedText(_aboutText, Qt::ElideRight, availableWidth)
: !about.isEmpty()
? font->m.elidedText(about, Qt::ElideRight, availableWidth)
: _delegate->rowIsMe(peer())
? tr::lng_status_connecting(tr::now)
: tr::lng_group_call_invited_status(tr::now)));
@@ -802,7 +822,7 @@ void Row::refreshStatus() {
? u"%1% %2"_q
.arg(std::round(_volume / 100.))
.arg(tr::lng_group_call_active(tr::now))
: (_state == State::RaisedHand)
: _raisedHandStatus
? tr::lng_group_call_raised_hand_status(tr::now)
: tr::lng_group_call_inactive(tr::now)),
_speaking);
@@ -833,6 +853,7 @@ MembersController::MembersController(
: _call(call)
, _peer(call->peer())
, _menuParent(menuParent)
, _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
, _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
, _coloredCrossLine(st::groupCallMemberColoredCrossLine) {
setupListChangeViewers(call);
@@ -929,19 +950,11 @@ void MembersController::setupListChangeViewers(not_null<GroupCall*> call) {
delegate()->peerListRefreshRows();
});
if (const auto row = findRow(event.wasJoinAs)) {
if (row->state() != Row::State::Invited) {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
}
removeRow(row);
}
if (findRow(event.nowJoinAs)) {
return;
} else if (auto row = createRowForMe()) {
if (row->state() != Row::State::Invited) {
_fullCountMin = _fullCountMin.current() + 1;
}
delegate()->peerListAppendRow(std::move(row));
}
}, _lifetime);
@@ -951,13 +964,7 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
_realCallRawValue = real;
_realId = real->id();
_fullCount = rpl::combine(
real->fullCountValue(),
_fullCountMin.value(),
_fullCountMax.value()
) | rpl::map([](int value, int min, int max) {
return std::max(std::clamp(value, min, max), 1);
});
_fullCount = real->fullCountValue();
real->participantsSliceAdded(
) | rpl::start_with_next([=] {
@@ -978,9 +985,6 @@ void MembersController::subscribeToChanges(not_null<Data::GroupCall*> real) {
if (isMe(participantPeer)) {
updateRow(row, nullptr);
} else {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
removeRow(row);
delegate()->peerListRefreshRows();
}
@@ -1018,37 +1022,41 @@ void MembersController::appendInvitedUsers() {
void MembersController::updateRow(
const std::optional<Data::GroupCall::Participant> &was,
const Data::GroupCall::Participant &now) {
auto reorderIfInvitedBeforeIndex = 0;
auto countChange = 0;
auto reorderIfInvitedBefore = 0;
auto checkPosition = (Row*)nullptr;
auto addedToBottom = (Row*)nullptr;
if (const auto row = findRow(now.peer)) {
if (row->state() == Row::State::Invited) {
reorderIfInvitedBeforeIndex = row->absoluteIndex();
countChange = 1;
reorderIfInvitedBefore = row->absoluteIndex();
}
updateRow(row, &now);
if ((now.speaking && (!was || !was->speaking))
|| (now.raisedHandRating != (was ? was->raisedHandRating : 0))
|| (!now.canSelfUnmute && was && was->canSelfUnmute)) {
checkRowPosition(row);
checkPosition = row;
}
} else if (auto row = createRow(now)) {
if (row->speaking()) {
delegate()->peerListPrependRow(std::move(row));
} else {
reorderIfInvitedBeforeIndex = delegate()->peerListFullRowsCount();
reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
if (now.raisedHandRating != 0) {
checkPosition = row.get();
} else {
addedToBottom = row.get();
}
delegate()->peerListAppendRow(std::move(row));
}
delegate()->peerListRefreshRows();
countChange = 1;
}
static constexpr auto kInvited = Row::State::Invited;
const auto reorder = [&] {
const auto count = reorderIfInvitedBeforeIndex;
const auto count = reorderIfInvitedBefore;
if (count <= 0) {
return false;
}
const auto row = delegate()->peerListRowAt(
reorderIfInvitedBeforeIndex - 1).get();
reorderIfInvitedBefore - 1).get();
return (static_cast<Row*>(row)->state() == kInvited);
}();
if (reorder) {
@@ -1056,12 +1064,25 @@ void MembersController::updateRow(
return static_cast<const Row&>(row).state() != kInvited;
});
}
if (countChange) {
const auto fullCountMin = _fullCountMin.current() + countChange;
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
if (checkPosition) {
checkRowPosition(checkPosition);
} else if (addedToBottom) {
const auto real = resolvedRealCall();
if (real && real->joinedToTop()) {
const auto proj = [&](const PeerListRow &other) {
const auto &real = static_cast<const Row&>(other);
return real.speaking()
? 2
: (&real == addedToBottom)
? 1
: 0;
};
delegate()->peerListSortRows([&](
const PeerListRow &a,
const PeerListRow &b) {
return proj(a) > proj(b);
});
}
_fullCountMin = fullCountMin;
}
}
@@ -1163,7 +1184,7 @@ void MembersController::checkRowPosition(not_null<Row*> row) {
// All force muted at the bottom, but 'row' still above others.
? (&real == row.get() ? 1ULL : 0ULL)
// All not force-muted lie between raised hands and speaking.
: (std::numeric_limits<uint64>::max() - 2);
: (kTop - 2);
};
const auto projForOther = [&](const PeerListRow &other) {
const auto &real = static_cast<const Row&>(other);
@@ -1218,11 +1239,6 @@ void MembersController::updateRow(
_soundingAnimation.stop();
}
if (!participant && wasInChat) {
if (const auto min = _fullCountMin.current()) {
_fullCountMin = min - 1;
}
}
delegate()->peerListUpdateRow(row);
}
@@ -1268,7 +1284,6 @@ void MembersController::prepare() {
; real && call && real->id() == call->id()) {
prepareRows(real);
} else if (auto row = createRowForMe()) {
_fullCountMin = (row->state() == Row::State::Invited) ? 0 : 1;
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
}
@@ -1289,7 +1304,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
auto foundMe = false;
auto changed = false;
const auto &participants = real->participants();
auto fullCountMin = 0;
auto count = delegate()->peerListFullRowsCount();
for (auto i = 0; i != count;) {
auto row = delegate()->peerListRowAt(i);
@@ -1304,7 +1318,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
participantPeer,
&Data::GroupCall::Participant::peer);
if (contains) {
++fullCountMin;
++i;
} else {
changed = true;
@@ -1323,9 +1336,6 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
? createRow(*i)
: createRowForMe();
if (row) {
if (row->state() != Row::State::Invited) {
++fullCountMin;
}
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
@@ -1333,20 +1343,12 @@ void MembersController::prepareRows(not_null<Data::GroupCall*> real) {
}
for (const auto &participant : participants) {
if (auto row = createRow(participant)) {
++fullCountMin;
changed = true;
delegate()->peerListAppendRow(std::move(row));
}
}
if (changed) {
delegate()->peerListRefreshRows();
if (_fullCountMax.current() < fullCountMin) {
_fullCountMax = fullCountMin;
}
_fullCountMin = fullCountMin;
if (real->participantsLoaded()) {
_fullCountMax = fullCountMin;
}
}
}
@@ -1378,6 +1380,44 @@ void MembersController::rowUpdateRow(not_null<Row*> row) {
delegate()->peerListUpdateRow(row);
}
void MembersController::rowScheduleRaisedHandStatusRemove(
not_null<Row*> row) {
const auto id = row->peer()->id;
const auto when = crl::now() + kKeepRaisedHandStatusDuration;
const auto i = _raisedHandStatusRemoveAt.find(id);
if (i != _raisedHandStatusRemoveAt.end()) {
i->second = when;
} else {
_raisedHandStatusRemoveAt.emplace(id, when);
}
scheduleRaisedHandStatusRemove();
}
void MembersController::scheduleRaisedHandStatusRemove() {
auto waiting = crl::time(0);
const auto now = crl::now();
for (auto i = begin(_raisedHandStatusRemoveAt)
; i != end(_raisedHandStatusRemoveAt);) {
if (i->second <= now) {
if (const auto row = delegate()->peerListFindRow(i->first)) {
static_cast<Row*>(row)->clearRaisedHandStatus();
}
i = _raisedHandStatusRemoveAt.erase(i);
} else {
if (!waiting || waiting > (i->second - now)) {
waiting = i->second - now;
}
++i;
}
}
if (waiting > 0) {
if (!_raisedHandStatusRemoveTimer.isActive()
|| _raisedHandStatusRemoveTimer.remainingTime() > waiting) {
_raisedHandStatusRemoveTimer.callOnce(waiting);
}
}
}
void MembersController::rowPaintIcon(
Painter &p,
QRect rect,
@@ -1531,7 +1571,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);
});
@@ -1555,7 +1595,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(
@@ -1564,9 +1608,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()) {
@@ -1574,16 +1616,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()) {
@@ -1747,8 +1789,7 @@ std::unique_ptr<Row> MembersController::createInvitedRow(
} // namespace
GroupMembers::GroupMembers(
Members::Members(
not_null<QWidget*> parent,
not_null<GroupCall*> call)
: RpWidget(parent)
@@ -1762,25 +1803,25 @@ GroupMembers::GroupMembers(
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
}
auto GroupMembers::toggleMuteRequests() const
auto Members::toggleMuteRequests() const
-> rpl::producer<Group::MuteRequest> {
return static_cast<MembersController*>(
_listController.get())->toggleMuteRequests();
}
auto GroupMembers::changeVolumeRequests() const
auto Members::changeVolumeRequests() const
-> rpl::producer<Group::VolumeRequest> {
return static_cast<MembersController*>(
_listController.get())->changeVolumeRequests();
}
auto GroupMembers::kickParticipantRequests() const
auto Members::kickParticipantRequests() const
-> rpl::producer<not_null<PeerData*>> {
return static_cast<MembersController*>(
_listController.get())->kickParticipantRequests();
}
int GroupMembers::desiredHeight() const {
int Members::desiredHeight() const {
const auto top = _addMember ? _addMember->height() : 0;
auto count = [&] {
if (const auto call = _call.get()) {
@@ -1798,7 +1839,7 @@ int GroupMembers::desiredHeight() const {
+ (use ? st::lineWidth : 0);
}
rpl::producer<int> GroupMembers::desiredHeightValue() const {
rpl::producer<int> Members::desiredHeightValue() const {
const auto controller = static_cast<MembersController*>(
_listController.get());
return rpl::combine(
@@ -1810,12 +1851,28 @@ rpl::producer<int> GroupMembers::desiredHeightValue() const {
});
}
void GroupMembers::setupAddMember(not_null<GroupCall*> call) {
void Members::setupAddMember(not_null<GroupCall*> call) {
using namespace rpl::mappers;
const auto peer = call->peer();
if (peer->isBroadcast()) {
_canAddMembers = false;
if (const auto channel = peer->asBroadcast()) {
_canAddMembers = rpl::single(
false
) | rpl::then(peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::GroupCall
) | rpl::map([=] {
return peer->groupCall();
}) | rpl::filter([=](Data::GroupCall *real) {
const auto call = _call.get();
return call && real && (real->id() == call->id());
}) | rpl::take(
1
) | rpl::map([=] {
return Data::PeerFlagValue(
channel,
MTPDchannel::Flag::f_username);
}) | rpl::flatten_latest());
} else {
_canAddMembers = Data::CanWriteValue(peer.get());
SubscribeToMigration(
@@ -1851,12 +1908,12 @@ void GroupMembers::setupAddMember(not_null<GroupCall*> call) {
}, lifetime());
}
rpl::producer<int> GroupMembers::fullCountValue() const {
rpl::producer<int> Members::fullCountValue() const {
return static_cast<MembersController*>(
_listController.get())->fullCountValue();
}
void GroupMembers::setupList() {
void Members::setupList() {
_listController->setStyleOverrides(&st::groupCallMembersList);
_list = _scroll->setOwnedWidget(object_ptr<ListWidget>(
this,
@@ -1877,11 +1934,11 @@ void GroupMembers::setupList() {
updateControlsGeometry();
}
void GroupMembers::resizeEvent(QResizeEvent *e) {
void Members::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void GroupMembers::resizeToList() {
void Members::resizeToList() {
if (!_list) {
return;
}
@@ -1898,7 +1955,7 @@ void GroupMembers::resizeToList() {
}
}
void GroupMembers::updateControlsGeometry() {
void Members::updateControlsGeometry() {
if (!_list) {
return;
}
@@ -1912,7 +1969,7 @@ void GroupMembers::updateControlsGeometry() {
_list->resizeToWidth(width());
}
void GroupMembers::setupFakeRoundCorners() {
void Members::setupFakeRoundCorners() {
const auto size = st::roundRadiusLarge;
const auto full = 3 * size;
const auto imagePartSize = size * cIntRetinaFactor();
@@ -1975,40 +2032,40 @@ void GroupMembers::setupFakeRoundCorners() {
}, lifetime());
}
void GroupMembers::peerListSetTitle(rpl::producer<QString> title) {
void Members::peerListSetTitle(rpl::producer<QString> title) {
}
void GroupMembers::peerListSetAdditionalTitle(rpl::producer<QString> title) {
void Members::peerListSetAdditionalTitle(rpl::producer<QString> title) {
}
void GroupMembers::peerListSetHideEmpty(bool hide) {
void Members::peerListSetHideEmpty(bool hide) {
}
bool GroupMembers::peerListIsRowChecked(not_null<PeerListRow*> row) {
bool Members::peerListIsRowChecked(not_null<PeerListRow*> row) {
return false;
}
void GroupMembers::peerListScrollToTop() {
void Members::peerListScrollToTop() {
}
int GroupMembers::peerListSelectedRowsCount() {
int Members::peerListSelectedRowsCount() {
return 0;
}
void GroupMembers::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
Unexpected("Item selection in Calls::GroupMembers.");
void Members::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
Unexpected("Item selection in Calls::Members.");
}
void GroupMembers::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
Unexpected("Item selection in Calls::GroupMembers.");
void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
Unexpected("Item selection in Calls::Members.");
}
void GroupMembers::peerListFinishSelectedRowsBunch() {
void Members::peerListFinishSelectedRowsBunch() {
}
void GroupMembers::peerListSetDescription(
void Members::peerListSetDescription(
object_ptr<Ui::FlatLabel> description) {
description.destroy();
}
} // namespace Calls
} // namespace Calls::Group

View File

@@ -19,19 +19,19 @@ class GroupCall;
} // namespace Data
namespace Calls {
class GroupCall;
} // namespace Calls
namespace Calls::Group {
namespace Group {
struct VolumeRequest;
struct MuteRequest;
} // namespace Group
class GroupCall;
class GroupMembers final
class Members final
: public Ui::RpWidget
, private PeerListContentDelegate {
public:
GroupMembers(
Members(
not_null<QWidget*> parent,
not_null<GroupCall*> call);

View File

@@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls::Group {
namespace {
constexpr auto kMaxGroupCallLength = 40;
void EditGroupCallTitleBox(
not_null<Ui::GenericBox*> box,
const QString &placeholder,
@@ -42,6 +44,7 @@ void EditGroupCallTitleBox(
st::groupCallField,
rpl::single(placeholder),
title));
input->setMaxLength(kMaxGroupCallLength);
box->setFocusCallback([=] {
input->setFocusFast();
});
@@ -514,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
@@ -672,6 +669,21 @@ void FillMenu(
BoxContext::GroupCallPanel));
}
}));
}
base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
not_null<Ui::Menu::Menu*> menu,
const QString &text,
Fn<void()> callback) {
return base::make_unique_q<Ui::Menu::Action>(
menu,
st::groupCallFinishMenu,
Ui::Menu::CreateAction(
menu,
text,
std::move(callback)),
nullptr,
nullptr);
}

View File

@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace Ui {
class DropdownMenu;
@@ -15,6 +16,11 @@ class GenericBox;
class BoxContent;
} // namespace Ui
namespace Ui::Menu {
class ItemBase;
class Menu;
} // namespace Ui::Menu
namespace Calls {
class GroupCall;
} // namespace Calls
@@ -45,4 +51,9 @@ void FillMenu(
Fn<void()> chooseJoinAs,
Fn<void(object_ptr<Ui::BoxContent>)> showBox);
[[nodiscard]] base::unique_qptr<Ui::Menu::ItemBase> MakeAttentionAction(
not_null<Ui::Menu::Menu*> menu,
const QString &text,
Fn<void()> callback);
} // namespace Calls::Group

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_group_settings.h"
#include "calls/calls_group_menu.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/platform/ui_platform_utility.h"
#include "ui/controls/call_mute_button.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/window.h"
@@ -22,7 +23,7 @@ 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.
#include "core/application.h"
@@ -48,10 +49,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication>
#include <QtGui/QWindow>
namespace Calls {
namespace Calls::Group {
namespace {
constexpr auto kSpacePushToTalkDelay = crl::time(250);
constexpr auto kRecordingAnimationDuration = crl::time(1200);
constexpr auto kRecordingOpacity = 0.6;
class InviteController final : public ParticipantsBoxController {
public:
@@ -78,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;
@@ -186,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);
@@ -246,7 +250,7 @@ std::unique_ptr<PeerListRow> InviteContactsController::createRow(
} // namespace
GroupPanel::GroupPanel(not_null<GroupCall*> call)
Panel::Panel(not_null<GroupCall*> call)
: _call(call)
, _peer(call->peer())
, _window(std::make_unique<Ui::Window>(Core::App().getModalParent()))
@@ -281,13 +285,16 @@ GroupPanel::GroupPanel(not_null<GroupCall*> call)
initControls();
initLayout();
showAndActivate();
setupJoinAsChangedToasts();
setupTitleChangedToasts();
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
@@ -295,25 +302,24 @@ GroupPanel::GroupPanel(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::WithEntities(name),
Ui::Text::RichLangValue),
.st = &st::defaultToast,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, widget()->lifetime());
}
GroupPanel::~GroupPanel() {
Panel::~Panel() {
if (_menu) {
_menu.destroy();
}
}
void GroupPanel::setupRealCallViewers(not_null<GroupCall*> call) {
void Panel::setupRealCallViewers(not_null<GroupCall*> call) {
const auto peer = call->peer();
peer->session().changes().peerFlagsValue(
peer,
@@ -329,21 +335,21 @@ void GroupPanel::setupRealCallViewers(not_null<GroupCall*> call) {
}, _window->lifetime());
}
bool GroupPanel::isActive() const {
bool Panel::isActive() const {
return _window->isActiveWindow()
&& _window->isVisible()
&& !(_window->windowState() & Qt::WindowMinimized);
}
void GroupPanel::minimize() {
void Panel::minimize() {
_window->setWindowState(_window->windowState() | Qt::WindowMinimized);
}
void GroupPanel::close() {
void Panel::close() {
_window->close();
}
void GroupPanel::showAndActivate() {
void Panel::showAndActivate() {
if (_window->isHidden()) {
_window->show();
}
@@ -356,7 +362,7 @@ void GroupPanel::showAndActivate() {
_window->setFocus();
}
void GroupPanel::migrate(not_null<ChannelData*> channel) {
void Panel::migrate(not_null<ChannelData*> channel) {
_peer = channel;
_peerLifetime.destroy();
subscribeToPeerChanges();
@@ -364,7 +370,7 @@ void GroupPanel::migrate(not_null<ChannelData*> channel) {
refreshTitle();
}
void GroupPanel::subscribeToPeerChanges() {
void Panel::subscribeToPeerChanges() {
Info::Profile::NameValue(
_peer
) | rpl::start_with_next([=](const TextWithEntities &name) {
@@ -372,7 +378,7 @@ void GroupPanel::subscribeToPeerChanges() {
}, _peerLifetime);
}
void GroupPanel::initWindow() {
void Panel::initWindow() {
_window->setAttribute(Qt::WA_OpaquePaintEvent);
_window->setAttribute(Qt::WA_NoSystemBackground);
_window->setWindowIcon(
@@ -405,13 +411,17 @@ void GroupPanel::initWindow() {
0,
widget()->width(),
st::groupCallMembersTop);
return titleRect.contains(widgetPoint)
return (titleRect.contains(widgetPoint)
&& (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint))
&& (!_menu || !_menu->geometry().contains(widgetPoint))
&& (!_recordingMark || !_recordingMark->geometry().contains(widgetPoint))
&& (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint)))
? (Flag::Move | Flag::Maximize)
: Flag::None;
});
}
void GroupPanel::initWidget() {
void Panel::initWidget() {
widget()->setMouseTracking(true);
widget()->paintRequest(
@@ -429,7 +439,7 @@ void GroupPanel::initWidget() {
}, widget()->lifetime());
}
void GroupPanel::endCall() {
void Panel::endCall() {
if (!_call) {
return;
} else if (!_call->peer()->canManageGroupCall()) {
@@ -437,13 +447,13 @@ void GroupPanel::endCall() {
return;
}
_layerBg->showBox(Box(
Group::LeaveBox,
LeaveBox,
_call,
false,
Group::BoxContext::GroupCallPanel));
BoxContext::GroupCallPanel));
}
void GroupPanel::initControls() {
void Panel::initControls() {
_mute->clicks(
) | rpl::filter([=](Qt::MouseButton button) {
return (button == Qt::LeftButton) && (_call != nullptr);
@@ -462,11 +472,11 @@ void GroupPanel::initControls() {
_hangup->setClickedCallback([=] { endCall(); });
_settings->setClickedCallback([=] {
if (_call) {
_layerBg->showBox(Box(Group::SettingsBox, _call));
_layerBg->showBox(Box(SettingsBox, _call));
}
});
_settings->setText(tr::lng_menu_settings());
_settings->setText(tr::lng_group_call_settings());
_hangup->setText(tr::lng_group_call_leave());
_members->desiredHeightValue(
@@ -477,7 +487,7 @@ void GroupPanel::initControls() {
initWithCall(_call);
}
void GroupPanel::initWithCall(GroupCall *call) {
void Panel::initWithCall(GroupCall *call) {
_callLifetime.destroy();
_call = call;
if (!_call) {
@@ -504,14 +514,14 @@ void GroupPanel::initWithCall(GroupCall *call) {
}, _callLifetime);
_members->toggleMuteRequests(
) | rpl::start_with_next([=](Group::MuteRequest request) {
) | rpl::start_with_next([=](MuteRequest request) {
if (_call) {
_call->toggleMute(request);
}
}, _callLifetime);
_members->changeVolumeRequests(
) | rpl::start_with_next([=](Group::VolumeRequest request) {
) | rpl::start_with_next([=](VolumeRequest request) {
if (_call) {
_call->changeVolume(request);
}
@@ -519,15 +529,33 @@ void GroupPanel::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::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
showBox,
showToast);
auto shareLink = std::move(shareLinkCallback);
_members->lifetime().add(std::move(shareLinkLifetime));
_members->addMembersRequests(
) | rpl::start_with_next([=] {
if (_call) {
addMembers();
if (_peer->isBroadcast() && _peer->asChannel()->hasUsername()) {
shareLink();
} else {
addMembers();
}
}
}, _callLifetime);
@@ -573,29 +601,91 @@ void GroupPanel::initWithCall(GroupCall *call) {
}, _callLifetime);
}
void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
void Panel::setupJoinAsChangedToasts() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
return (event.wasJoinAs != event.nowJoinAs);
}) | rpl::map([=] {
return _call->stateValue() | rpl::filter([](State state) {
return (state == State::Joined);
}) | rpl::take(1);
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
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),
});
}, widget()->lifetime());
}
void Panel::setupTitleChangedToasts() {
_call->titleChanged(
) | rpl::filter([=] {
return _peer->groupCall() && _peer->groupCall()->id() == _call->id();
}) | rpl::map([=] {
return _peer->groupCall()->title().isEmpty()
? _peer->name
: _peer->groupCall()->title();
}) | rpl::start_with_next([=](const QString &title) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_title_changed(
tr::now,
lt_title,
Ui::Text::Bold(title),
Ui::Text::WithEntities),
});
}, widget()->lifetime());
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
_titleText = real->titleValue();
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
_recordingMark.destroy();
} else if (recording && !_recordingMark) {
struct State {
Ui::Animations::Simple animation;
base::Timer timer;
bool opaque = true;
};
_recordingMark.create(widget());
_recordingMark->show();
const auto state = _recordingMark->lifetime().make_state<State>();
const auto size = st::groupCallRecordingMark;
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;
state->opaque = !opaque;
state->animation.start(
[=] { _recordingMark->update(); },
opaque ? 1. : kRecordingOpacity,
opaque ? kRecordingOpacity : 1.,
kRecordingAnimationDuration);
};
state->timer.setCallback(animate);
state->timer.callEach(kRecordingAnimationDuration);
animate();
_recordingMark->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(_recordingMark.data());
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::groupCallMemberMutedIcon);
p.setOpacity(state->animation.value(
state->opaque ? 1. : kRecordingOpacity));
p.drawEllipse(skip, skip, size, size);
}, _recordingMark->lifetime());
}
@@ -609,13 +699,16 @@ void GroupPanel::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);
@@ -633,7 +726,7 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
rpl::single(
_call->joinAs()
) | rpl::then(_call->rejoinEvents(
) | rpl::map([](const Group::RejoinEvent &event) {
) | rpl::map([](const RejoinEvent &event) {
return event.nowJoinAs;
})) | rpl::start_with_next([=](not_null<PeerData*> joinAs) {
auto joinAsToggle = object_ptr<Ui::UserpicButton>(
@@ -647,6 +740,7 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
_joinAsToggle->setClickedCallback([=] {
chooseJoinAs();
});
updateControlsGeometry();
}, widget()->lifetime());
} else {
_menuToggle.destroy();
@@ -655,9 +749,9 @@ void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
updateControlsGeometry();
}
void GroupPanel::chooseJoinAs() {
const auto context = Group::ChooseJoinAsProcess::Context::Switch;
const auto callback = [=](Group::JoinInfo info) {
void Panel::chooseJoinAs() {
const auto context = ChooseJoinAsProcess::Context::Switch;
const auto callback = [=](JoinInfo info) {
if (_call) {
_call->rejoinAs(info);
}
@@ -666,7 +760,10 @@ void GroupPanel::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,
@@ -677,12 +774,12 @@ void GroupPanel::chooseJoinAs() {
_call->joinAs());
}
void GroupPanel::showMainMenu() {
void Panel::showMainMenu() {
if (_menu || !_call) {
return;
}
_menu.create(widget(), st::groupCallDropdownMenu);
Group::FillMenu(
FillMenu(
_menu.data(),
_peer,
_call,
@@ -724,7 +821,7 @@ void GroupPanel::showMainMenu() {
}
}
void GroupPanel::addMembers() {
void Panel::addMembers() {
const auto real = _peer->groupCall();
if (!_call || !real || real->id() != _call->id()) {
return;
@@ -760,28 +857,24 @@ void GroupPanel::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.");
@@ -824,7 +917,7 @@ void GroupPanel::addMembers() {
finish();
};
auto box = Box(
Group::ConfirmBox,
ConfirmBox,
TextWithEntities{ text },
tr::lng_participant_invite(),
[=] { inviteWithAdd(users, nonMembers, finishWithConfirm); });
@@ -865,15 +958,22 @@ void GroupPanel::addMembers() {
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
}
void GroupPanel::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(),
@@ -882,50 +982,60 @@ void GroupPanel::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 GroupPanel::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);
}
}
void GroupPanel::initLayout() {
void Panel::initLayout() {
initGeometry();
#ifndef Q_OS_MAC
_controls->raise();
Ui::Platform::TitleControlsLayoutChanged(
) | rpl::start_with_next([=] {
// _menuToggle geometry depends on _controls arrangement.
crl::on_main(widget(), [=] { updateControlsGeometry(); });
}, widget()->lifetime());
#endif // !Q_OS_MAC
}
void GroupPanel::showControls() {
void Panel::showControls() {
Expects(_call != nullptr);
widget()->showChildren();
}
void GroupPanel::closeBeforeDestroy() {
void Panel::closeBeforeDestroy() {
_window->close();
initWithCall(nullptr);
}
void GroupPanel::initGeometry() {
void Panel::initGeometry() {
const auto center = Core::App().getPointForCallPanelCenter();
const auto rect = QRect(0, 0, st::groupCallWidth, st::groupCallHeight);
_window->setGeometry(rect.translated(center - rect.center()));
@@ -934,7 +1044,7 @@ void GroupPanel::initGeometry() {
updateControlsGeometry();
}
QRect GroupPanel::computeTitleRect() const {
QRect Panel::computeTitleRect() const {
const auto skip = st::groupCallTitleTop;
const auto remove = skip + (_menuToggle
? (_menuToggle->width() + st::groupCallMenuTogglePosition.x())
@@ -943,7 +1053,7 @@ QRect GroupPanel::computeTitleRect() const {
: 0);
const auto width = widget()->width();
#ifdef Q_OS_MAC
return QRect(70, 0, width - skip - 70, 28);
return QRect(70, 0, width - remove - 70, 28);
#else // Q_OS_MAC
const auto controls = _controls->geometry();
const auto right = controls.x() + controls.width() + skip;
@@ -953,7 +1063,7 @@ QRect GroupPanel::computeTitleRect() const {
#endif // !Q_OS_MAC
}
void GroupPanel::updateControlsGeometry() {
void Panel::updateControlsGeometry() {
if (widget()->size().isEmpty()) {
return;
}
@@ -1011,7 +1121,7 @@ void GroupPanel::updateControlsGeometry() {
}
}
void GroupPanel::refreshTitle() {
void Panel::refreshTitle() {
if (!_title) {
auto text = rpl::combine(
Info::Profile::NameValue(_peer),
@@ -1036,7 +1146,9 @@ void GroupPanel::refreshTitle() {
widget(),
tr::lng_group_call_members(
lt_count_decimal,
_members->fullCountValue() | tr::to_count()),
_members->fullCountValue() | rpl::map([](int value) {
return (value > 0) ? float64(value) : 1.;
})),
st::groupCallSubtitleLabel);
_subtitle->show();
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
@@ -1052,7 +1164,7 @@ void GroupPanel::refreshTitle() {
top);
}
void GroupPanel::refreshTitleGeometry() {
void Panel::refreshTitleGeometry() {
if (!_title) {
return;
}
@@ -1091,7 +1203,7 @@ void GroupPanel::refreshTitleGeometry() {
}
}
void GroupPanel::paint(QRect clip) {
void Panel::paint(QRect clip) {
Painter p(widget());
auto region = QRegion(clip);
@@ -1100,7 +1212,7 @@ void GroupPanel::paint(QRect clip) {
}
}
bool GroupPanel::handleClose() {
bool Panel::handleClose() {
if (_call) {
_window->hide();
return true;
@@ -1108,8 +1220,8 @@ bool GroupPanel::handleClose() {
return false;
}
not_null<Ui::RpWidget*> GroupPanel::widget() const {
not_null<Ui::RpWidget*> Panel::widget() const {
return _window->body();
}
} // namespace Calls
} // namespace Calls::Group

View File

@@ -48,17 +48,14 @@ struct CallSignalBars;
struct CallBodyLayout;
} // namespace style
namespace Calls {
namespace Calls::Group {
class Userpic;
class SignalBars;
class Members;
class GroupMembers;
class GroupPanel final {
class Panel final {
public:
GroupPanel(not_null<GroupCall*> call);
~GroupPanel();
Panel(not_null<GroupCall*> call);
~Panel();
[[nodiscard]] bool isActive() const;
void minimize();
@@ -79,6 +76,8 @@ private:
void initWithCall(GroupCall *call);
void initLayout();
void initGeometry();
void setupJoinAsChangedToasts();
void setupTitleChangedToasts();
bool handleClose();
@@ -90,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();
@@ -119,9 +118,9 @@ private:
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<GroupMembers> _members;
object_ptr<Members> _members;
rpl::variable<QString> _titleText;
Group::ChooseJoinAsProcess _joinAsProcess;
ChooseJoinAsProcess _joinAsProcess;
object_ptr<Ui::CallButton> _settings;
std::unique_ptr<Ui::CallMuteButton> _mute;
@@ -131,4 +130,4 @@ private:
};
} // namespace Calls
} // namespace Calls::Group

View File

@@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/input_fields.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "lang/lang_keys.h"
#include "boxes/share_box.h"
#include "history/history_message.h" // GetErrorTextForSending.
@@ -216,14 +216,6 @@ void SettingsBox(
const auto weakBox = Ui::MakeWeak(box);
struct State {
State(not_null<Main::Session*> session) : session(session) {
}
~State() {
session->api().request(linkListenerRequestId).cancel();
session->api().request(linkSpeakerRequestId).cancel();
}
not_null<Main::Session*> session;
rpl::event_stream<QString> outputNameStream;
rpl::event_stream<QString> inputNameStream;
std::unique_ptr<Webrtc::AudioInputTester> micTester;
@@ -231,14 +223,10 @@ void SettingsBox(
float micLevel = 0.;
Ui::Animations::Simple micLevelAnimation;
base::Timer levelUpdateTimer;
std::optional<QString> linkSpeaker;
QString linkListener;
bool generatingLink = false;
mtpRequestId linkListenerRequestId = 0;
mtpRequestId linkSpeakerRequestId = 0;
};
const auto peer = call->peer();
const auto state = box->lifetime().make_state<State>(&peer->session());
const auto state = box->lifetime().make_state<State>();
const auto real = peer->groupCall();
const auto id = call->id();
const auto goodReal = (real && real->id() == id);
@@ -538,74 +526,26 @@ void SettingsBox(
//AddDivider(layout);
//AddSkip(layout);
if (!peer->canManageGroupCall()) {
state->linkSpeaker = QString();
}
auto shareLink = Fn<void()>();
if (peer->isChannel()
&& peer->asChannel()->hasUsername()
&& goodReal) {
const auto input = real->input();
const auto shareReady = [=] {
if (!state->linkSpeaker.has_value()
|| state->linkListener.isEmpty()) {
return false;
}
const auto showToast = crl::guard(box, [=](QString text) {
Ui::Toast::Show(
box->getDelegate()->outerContainer(),
text);
const auto showBox = crl::guard(box, [=](
object_ptr<Ui::BoxContent> next) {
box->getDelegate()->show(std::move(next));
});
const auto showToast = crl::guard(box, [=](QString text) {
Ui::ShowMultilineToast({
.parentOverride = box->getDelegate()->outerContainer(),
.text = { text },
});
box->getDelegate()->show(ShareInviteLinkBox(
peer,
*state->linkSpeaker,
state->linkListener,
showToast));
return true;
};
shareLink = [=] {
if (shareReady() || state->generatingLink) {
return;
}
state->generatingLink = true;
state->linkListenerRequestId = peer->session().api().request(
MTPphone_ExportGroupCallInvite(
MTP_flags(0),
input
)
).done(crl::guard(box, [=](
const MTPphone_ExportedGroupCallInvite &result) {
state->linkListenerRequestId = 0;
result.match([&](
const MTPDphone_exportedGroupCallInvite &data) {
state->linkListener = qs(data.vlink());
shareReady();
});
})).send();
if (!state->linkSpeaker.has_value()) {
using Flag = MTPphone_ExportGroupCallInvite::Flag;
state->linkSpeakerRequestId = peer->session().api().request(
MTPphone_ExportGroupCallInvite(
MTP_flags(Flag::f_can_self_unmute),
input
)).done(crl::guard(box, [=](
const MTPphone_ExportedGroupCallInvite &result) {
state->linkSpeakerRequestId = 0;
result.match([&](
const MTPDphone_exportedGroupCallInvite &data) {
state->linkSpeaker = qs(data.vlink());
shareReady();
});
})).fail([=] {
state->linkSpeakerRequestId = 0;
state->linkSpeaker = QString();
shareReady();
}).send();
}
};
});
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
peer,
showBox,
showToast);
shareLink = std::move(shareLinkCallback);
box->lifetime().add(std::move(shareLinkLifetime));
} else {
const auto lookupLink = [=] {
if (const auto group = peer->asMegagroup()) {
@@ -633,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;
};
@@ -698,4 +639,85 @@ void SettingsBox(
});
}
std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
not_null<PeerData*> peer,
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
Fn<void(QString)> showToast) {
auto lifetime = rpl::lifetime();
struct State {
State(not_null<Main::Session*> session) : session(session) {
}
~State() {
session->api().request(linkListenerRequestId).cancel();
session->api().request(linkSpeakerRequestId).cancel();
}
not_null<Main::Session*> session;
std::optional<QString> linkSpeaker;
QString linkListener;
mtpRequestId linkListenerRequestId = 0;
mtpRequestId linkSpeakerRequestId = 0;
bool generatingLink = false;
};
const auto state = lifetime.make_state<State>(&peer->session());
if (!peer->canManageGroupCall()) {
state->linkSpeaker = QString();
}
const auto shareReady = [=] {
if (!state->linkSpeaker.has_value()
|| state->linkListener.isEmpty()) {
return false;
}
showBox(ShareInviteLinkBox(
peer,
*state->linkSpeaker,
state->linkListener,
showToast));
return true;
};
auto callback = [=] {
const auto real = peer->groupCall();
if (shareReady() || state->generatingLink || !real) {
return;
}
state->generatingLink = true;
state->linkListenerRequestId = peer->session().api().request(
MTPphone_ExportGroupCallInvite(
MTP_flags(0),
real->input()
)
).done([=](const MTPphone_ExportedGroupCallInvite &result) {
state->linkListenerRequestId = 0;
result.match([&](
const MTPDphone_exportedGroupCallInvite &data) {
state->linkListener = qs(data.vlink());
shareReady();
});
}).send();
if (!state->linkSpeaker.has_value()) {
using Flag = MTPphone_ExportGroupCallInvite::Flag;
state->linkSpeakerRequestId = peer->session().api().request(
MTPphone_ExportGroupCallInvite(
MTP_flags(Flag::f_can_self_unmute),
real->input()
)).done([=](const MTPphone_ExportedGroupCallInvite &result) {
state->linkSpeakerRequestId = 0;
result.match([&](
const MTPDphone_exportedGroupCallInvite &data) {
state->linkSpeaker = qs(data.vlink());
shareReady();
});
}).fail([=] {
state->linkSpeakerRequestId = 0;
state->linkSpeaker = QString();
shareReady();
}).send();
}
};
return { std::move(callback), std::move(lifetime) };
}
} // namespace Calls::Group

View File

@@ -19,4 +19,9 @@ void SettingsBox(
not_null<Ui::GenericBox*> box,
not_null<GroupCall*> call);
[[nodiscard]] std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
not_null<PeerData*> peer,
Fn<void(object_ptr<Ui::BoxContent>)> showBox,
Fn<void(QString)> showToast);
} // namespace Calls::Group

View File

@@ -65,8 +65,11 @@ void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
void Instance::startOrJoinGroupCall(
not_null<PeerData*> peer,
const QString &joinHash) {
const auto context = peer->groupCall()
const QString &joinHash,
bool confirmNeeded) {
const auto context = confirmNeeded
? Group::ChooseJoinAsProcess::Context::JoinWithConfirm
: peer->groupCall()
? Group::ChooseJoinAsProcess::Context::Join
: Group::ChooseJoinAsProcess::Context::Create;
_chooseJoinAs.start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
@@ -224,7 +227,7 @@ void Instance::createGroupCall(
destroyGroupCall(raw);
}, raw->lifetime());
_currentGroupCallPanel = std::make_unique<GroupPanel>(raw);
_currentGroupCallPanel = std::make_unique<Group::Panel>(raw);
_currentGroupCall = std::move(call);
_currentGroupCallChanges.fire_copy(raw);
}
@@ -320,9 +323,9 @@ void Instance::handleUpdate(
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
handleSignalingData(session, data);
}, [&](const MTPDupdateGroupCall &data) {
handleGroupCallUpdate(session, data.vcall());
handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallParticipants &data) {
handleGroupCallUpdate(session, data);
handleGroupCallUpdate(session, update);
}, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate.");
});
@@ -407,31 +410,38 @@ void Instance::handleCallUpdate(
void Instance::handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPGroupCall &call) {
const auto callId = call.match([](const auto &data) {
return data.vid().v;
const MTPUpdate &update) {
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
return data.vcall().match([](const auto &data) {
return data.vid().v;
});
}, [](const MTPDupdateGroupCallParticipants &data) {
return data.vcall().match([&](const MTPDinputGroupCall &data) {
return data.vid().v;
});
}, [](const auto &) -> uint64 {
Unexpected("Type in Instance::handleGroupCallUpdate.");
});
if (const auto existing = session->data().groupCall(callId)) {
existing->applyUpdate(call);
existing->enqueueUpdate(update);
} else {
applyGroupCallUpdateChecked(session, update);
}
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(call);
update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
}, [](const auto &) {
});
}
}
void Instance::handleGroupCallUpdate(
void Instance::applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPDupdateGroupCallParticipants &update) {
const auto callId = update.vcall().match([](const auto &data) {
return data.vid().v;
});
if (const auto existing = session->data().groupCall(callId)) {
existing->applyUpdate(update);
}
const MTPUpdate &update) {
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)
&& (_currentGroupCall->id() == callId)) {
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(update);
}
}

View File

@@ -26,12 +26,12 @@ class Session;
namespace Calls::Group {
struct JoinInfo;
class Panel;
} // namespace Calls::Group
namespace Calls {
class Panel;
class GroupPanel;
class Instance
: private Call::Delegate
@@ -45,10 +45,17 @@ public:
void startOutgoingCall(not_null<UserData*> user, bool video);
void startOrJoinGroupCall(
not_null<PeerData*> peer,
const QString &joinHash = QString());
const QString &joinHash = QString(),
bool confirmNeeded = false);
void handleUpdate(
not_null<Main::Session*> session,
const MTPUpdate &update);
// Called by Data::GroupCall when it is appropriate by the 'version'.
void applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPUpdate &update);
void showInfoPanel(not_null<Call*> call);
void showInfoPanel(not_null<GroupCall*> call);
[[nodiscard]] Call *currentCall() const;
@@ -129,10 +136,7 @@ private:
const MTPDupdatePhoneCallSignalingData &data);
void handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPGroupCall &call);
void handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPDupdateGroupCallParticipants &update);
const MTPUpdate &update);
DhConfig _dhConfig;
@@ -146,7 +150,7 @@ private:
std::unique_ptr<GroupCall> _currentGroupCall;
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
std::unique_ptr<GroupPanel> _currentGroupCallPanel;
std::unique_ptr<Group::Panel> _currentGroupCallPanel;
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;

View File

@@ -406,7 +406,6 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
mrows.push_back({ i->second });
}
} else if (_channel && _channel->isMegagroup()) {
QMultiMap<int32, UserData*> ordered;
if (_channel->lastParticipantsRequestNeeded()) {
_channel->session().api().requestLastParticipants(_channel);
} else {
@@ -437,7 +436,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
} else if (_type == Type::BotCommands) {
bool listAllSuggestions = _filter.isEmpty();
bool hasUsername = _filter.indexOf('@') > 0;
QMap<UserData*, bool> bots;
base::flat_map<UserData*, bool> bots;
int32 cnt = 0;
if (_chat) {
if (_chat->noParticipantInfo()) {
@@ -452,7 +451,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->botInfo->commands.isEmpty()) {
continue;
}
bots.insert(user, true);
bots.emplace(user, true);
cnt += user->botInfo->commands.size();
}
}
@@ -461,7 +460,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
_user->session().api().requestFullPeer(_user);
}
cnt = _user->botInfo->commands.size();
bots.insert(_user, true);
bots.emplace(_user, true);
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->bots.empty()) {
if (!_channel->mgInfo->botStatus) {
@@ -477,7 +476,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
if (user->botInfo->commands.isEmpty()) {
continue;
}
bots.insert(user, true);
bots.emplace(user, true);
cnt += user->botInfo->commands.size();
}
}
@@ -511,9 +510,9 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
}
}
}
if (!bots.isEmpty()) {
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
UserData *user = i.key();
if (!bots.empty()) {
for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
UserData *user = i->first;
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
if (!listAllSuggestions) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;

View File

@@ -348,7 +348,7 @@ QImage TabbedPanel::grabForAnimation() {
_a_show = base::take(showAnimation);
_showAnimation = base::take(showAnimationData);
_a_opacity = base::take(opacityAnimation);
_cache = base::take(_cache);
_cache = base::take(cache);
return result;
}

View File

@@ -1022,7 +1022,7 @@ void Application::unregisterLeaveSubscription(not_null<QWidget*> widget) {
#ifdef Q_OS_MAC
_leaveSubscriptions = std::move(
_leaveSubscriptions
) | ranges::action::remove_if([&](const LeaveSubscription &subscription) {
) | ranges::actions::remove_if([&](const LeaveSubscription &subscription) {
auto pointer = subscription.pointer.data();
return !pointer || (pointer == widget);
});

View File

@@ -117,7 +117,36 @@ std::map<int, const char*> BetaLogs() {
{
2006002,
"- Fix text disappearing because of cloud drafts sync.\n"
}
},
{
2006003,
"- Fix audio device selection in voice chats.\n"
"- Fix blinking self profile photo "
"in case the profile photo privacy is used.\n"
"- Fix voice chat admin menu on macOS.\n"
},
{
2006004,
"- Fix freeze in voice chats.\n"
"- Make default interface scale 110% on macOS Retina screens.\n"
},
{
2006005,
"- Improvements and fixes in new voice chat features.\n"
},
{
2006007,
"- Improve voice chat participants list updating.\n"
},
{
2006008,
"- Fix connecting and getting allowed to speak on voice chats.\n"
"- MPRIS support on Linux.\n"
},
};
};

View File

@@ -786,7 +786,6 @@ void LastCrashedWindow::updateControls() {
h += _networkSettings.height() + padding;
}
QRect scr(QApplication::primaryScreen()->availableGeometry());
QSize s(2 * padding + QFontMetrics(_label.font()).horizontalAdvance(qsl("Last time Telegram Desktop was not closed properly.")) + padding + _networkSettings.width(), h);
if (s == size()) {
resizeEvent(0);

View File

@@ -94,7 +94,7 @@ QString filedialogDefaultName(
const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/'))
+ base;
name = nameBase + extension;
for (int i = 0; QFileInfo(name).exists(); ++i) {
for (int i = 0; QFileInfo::exists(name); ++i) {
name = nameBase + qsl(" (%1)").arg(i + 2) + extension;
}
}
@@ -115,7 +115,7 @@ QString filedialogNextFilename(
const auto dir = directory.absolutePath();
const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/')) + prefix;
auto result = nameBase + extension;
for (int i = 0; result.toLower() != cur.toLower() && QFileInfo(result).exists(); ++i) {
for (int i = 0; result.toLower() != cur.toLower() && QFileInfo::exists(result); ++i) {
result = nameBase + qsl(" (%1)").arg(i + 2) + extension;
}
return result;

View File

@@ -209,7 +209,11 @@ void Sandbox::setupScreenScale() {
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(qEnvironmentVariable("QT_SCREEN_SCALE_FACTORS")));
}
style::SetDevicePixelRatio(int(ratio));
cSetScreenScale(style::kScaleDefault);
if (Platform::IsMac() && ratio == 2.) {
cSetScreenScale(110); // 110% for Retina screens by default.
} else {
cSetScreenScale(style::kScaleDefault);
}
}
}

View File

@@ -250,7 +250,6 @@ QString ExtractFilename(const QString &url) {
bool UnpackUpdate(const QString &filepath) {
QFile input(filepath);
QByteArray packed;
if (!input.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return false;

View File

@@ -517,7 +517,7 @@ QString translitLetterRusEng(QChar letter, QChar next, int32 &toSkip) {
fastLetterRusEng.insert(QString::fromUtf8("ч").at(0), qsl("ch"));
fastLetterRusEng.insert(QString::fromUtf8("ш").at(0), qsl("sh"));
fastLetterRusEng.insert(QString::fromUtf8("щ").at(0), qsl("sch"));
fastLetterRusEng.insert(QString::fromUtf8("ъ").at(0), qsl(""));
fastLetterRusEng.insert(QString::fromUtf8("ъ").at(0), QString());
fastLetterRusEng.insert(QString::fromUtf8("э").at(0), qsl("e"));
fastLetterRusEng.insert(QString::fromUtf8("ю").at(0), qsl("yu"));
fastLetterRusEng.insert(QString::fromUtf8("я").at(0), qsl("ya"));
@@ -526,7 +526,7 @@ QString translitLetterRusEng(QChar letter, QChar next, int32 &toSkip) {
fastLetterRusEng.insert(QString::fromUtf8("и").at(0), qsl("i"));
fastLetterRusEng.insert(QString::fromUtf8("к").at(0), qsl("k"));
fastLetterRusEng.insert(QString::fromUtf8("ы").at(0), qsl("y"));
fastLetterRusEng.insert(QString::fromUtf8("ь").at(0), qsl(""));
fastLetterRusEng.insert(QString::fromUtf8("ь").at(0), QString());
}
QHash<QChar, QString>::const_iterator j = fastLetterRusEng.constFind(letter);
if (j != fastLetterRusEng.cend()) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -213,7 +213,7 @@ QString FileNameUnsafe(
}
QString nameBase = path + nameStart;
name = nameBase + extension;
for (int i = 0; QFileInfo(name).exists(); ++i) {
for (int i = 0; QFileInfo::exists(name); ++i) {
name = nameBase + QString(" (%1)").arg(i + 2) + extension;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -488,10 +488,12 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
: result->nameOrPhone;
result->setName(fname, lname, pname, uname);
if (const auto photo = data.vphoto()) {
result->setPhoto(*photo);
} else {
result->setPhoto(MTP_userProfilePhotoEmpty());
if (!minimal || data.is_apply_min_photo()) {
if (const auto photo = data.vphoto()) {
result->setPhoto(*photo);
} else {
result->setPhoto(MTP_userProfilePhotoEmpty());
}
}
if (const auto accessHash = data.vaccess_hash()) {
result->setAccessHash(accessHash->v);

View File

@@ -1093,7 +1093,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
}
}
ranges::action::sort(
ranges::actions::sort(
result,
std::greater<>(),
&StickerWithDate::date);

View File

@@ -782,7 +782,7 @@ void InnerWidget::paintPeerSearchResult(
QRect tr(nameleft, st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip, namewidth, st::dialogsTextFont->height);
p.setFont(st::dialogsTextFont);
QString username = peer->userName();
if (!active && username.toLower().startsWith(_peerSearchQuery)) {
if (!active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
auto first = '@' + username.mid(0, _peerSearchQuery.size());
auto second = username.mid(_peerSearchQuery.size());
auto w = st::dialogsTextFont->width(first);

View File

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

View File

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

View File

@@ -2628,14 +2628,14 @@ MessageIdsList HistoryInner::getSelectedItems() const {
auto result = ranges::make_subrange(
_selected.begin(),
_selected.end()
) | view::filter([](const auto &selected) {
) | views::filter([](const auto &selected) {
const auto item = selected.first;
return item && item->toHistoryMessage() && (item->id > 0);
}) | view::transform([](const auto &selected) {
}) | views::transform([](const auto &selected) {
return selected.first->fullId();
}) | to_vector;
result |= action::sort(ordered_less{}, [](const FullMsgId &msgId) {
result |= actions::sort(ordered_less{}, [](const FullMsgId &msgId) {
return msgId.channel ? msgId.msg : (msgId.msg - ServerMaxMsgId);
});
return result;

View File

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

View File

@@ -1183,8 +1183,6 @@ void ComposeControls::initAutocomplete() {
}
};
const auto insertMention = [=](not_null<UserData*> user) {
auto replacement = QString();
auto entityTag = QString();
if (user->username.isEmpty()) {
_field->insertTag(
user->firstName.isEmpty() ? user->name : user->firstName,

View File

@@ -2004,7 +2004,7 @@ void ListWidget::performDrag() {
}
TextWithEntities sel;
QList<QUrl> urls;
//QList<QUrl> urls;
if (uponSelected) {
// sel = getSelectedText();
} else if (pressedHandler) {

View File

@@ -317,14 +317,14 @@ void ListController::collapse() {
return;
}
const auto remove = count - (kFirstPage - kLeavePreloaded);
ranges::action::reverse(_preloaded);
ranges::actions::reverse(_preloaded);
_preloaded.reserve(_preloaded.size() + remove);
for (auto i = 0; i != remove; ++i) {
const auto row = delegate()->peerListRowAt(count - i - 1);
_preloaded.push_back(row->peer()->asUser());
delegate()->peerListRemoveRow(row);
}
ranges::action::reverse(_preloaded);
ranges::actions::reverse(_preloaded);
delegate()->peerListRefreshRows();
const auto now = count - remove;

View File

@@ -213,7 +213,7 @@ void Widget::startShowAnimation() {
showChildren();
auto image = grabForPanelAnimation();
_a_opacity = base::take(opacityAnimation);
_cache = base::take(_cache);
_cache = base::take(cache);
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomLeft);
auto inner = rect().marginsRemoved(st::emojiPanMargins);

View File

@@ -153,7 +153,7 @@ bool FileParser::readKeyValue(const char *&from, const char *end) {
}
QByteArray FileParser::ReadFile(const QString &absolutePath, const QString &relativePath) {
QFile file(QFileInfo(relativePath).exists() ? relativePath : absolutePath);
QFile file(QFileInfo::exists(relativePath) ? relativePath : absolutePath);
if (!file.open(QIODevice::ReadOnly)) {
Ui::Integration::Instance().writeLogEntry(u"Lang Error: Could not open file at '%1' ('%2')"_q.arg(relativePath, absolutePath));
return QByteArray();

View File

@@ -151,7 +151,7 @@ private:
for (QStringList::const_iterator i = oldlogs.cbegin(), e = oldlogs.cend(); i != e; ++i) {
QString oldlog = cWorkingDir() + *i, oldlogend = i->mid(qstr("log_start").size());
if (oldlogend.size() == 1 + qstr(".txt").size() && oldlogend.at(0).isDigit() && oldlogend.midRef(1) == qstr(".txt")) {
bool removed = QFile(*i).remove();
bool removed = QFile(oldlog).remove();
LOG(("Old start log '%1' found, deleted: %2").arg(*i, Logs::b(removed)));
}
}
@@ -265,7 +265,7 @@ bool DebugModeEnabled = false;
void MoveOldDataFiles(const QString &wasDir) {
QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config");
if (data.exists() && dataConfig.exists() && !QFileInfo(cWorkingDir() + "data").exists() && !QFileInfo(cWorkingDir() + "data_config").exists()) { // move to home dir
if (data.exists() && dataConfig.exists() && !QFileInfo::exists(cWorkingDir() + "data") && !QFileInfo::exists(cWorkingDir() + "data_config")) { // move to home dir
LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir(), wasDir));
if (data.copy(cWorkingDir() + "data")) {
LOG(("Copied 'data' to home dir"));

View File

@@ -1795,8 +1795,6 @@ void MainWidget::showNewSection(
Ui::hideSettingsAndLayer();
}
QPixmap animCache;
_controller->dialogsListFocused().set(false, true);
_a_dialogsWidth.stop();

View File

@@ -194,7 +194,7 @@ void Float::paintEvent(QPaintEvent *e) {
const auto progress = playback ? playback->value() : 1.;
if (progress > 0.) {
auto pen = st::historyVideoMessageProgressFg->p;
auto was = p.pen();
//auto was = p.pen();
pen.setWidth(st::radialLine);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);

View File

@@ -476,7 +476,6 @@ void Widget::handleSongUpdate(const TrackState &state) {
}
void Widget::updateTimeText(const TrackState &state) {
QString time;
qint64 position = 0, length = 0, display = 0;
const auto frequency = state.frequency;
const auto document = state.id.audio();

View File

@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media.h"
#include "ui/image/image.h"
#include "main/main_session.h"
#include "core/crash_reports.h"
#include "app.h"
#include "styles/style_media_view.h"
@@ -37,6 +38,42 @@ int Round(float64 value) {
using Context = GroupThumbs::Context;
using Key = GroupThumbs::Key;
[[nodiscard]] QString DebugSerializeMsgId(FullMsgId itemId) {
return QString("msg%1_%2").arg(itemId.channel).arg(itemId.msg);
}
[[nodiscard]] QString DebugSerializePeer(PeerId peerId) {
return peerIsUser(peerId)
? QString("user%1").arg(peerToUser(peerId))
: peerIsChat(peerId)
? QString("chat%1").arg(peerToChat(peerId))
: QString("channel%1").arg(peerToChannel(peerId));
}
[[nodiscard]] QString DebugSerializeKey(const Key &key) {
return v::match(key, [&](PhotoId photoId) {
return QString("photo%1").arg(photoId);
}, [](FullMsgId itemId) {
return DebugSerializeMsgId(itemId);
}, [&](GroupThumbs::CollageKey key) {
return QString("collage%1").arg(key.index);
});
}
[[nodiscard]] QString DebugSerializeContext(const Context &context) {
return v::match(context, [](PeerId peerId) {
return DebugSerializePeer(peerId);
}, [](MessageGroupId groupId) {
return QString("group_%1_%2"
).arg(DebugSerializePeer(groupId.peer)
).arg(groupId.value);
}, [](FullMsgId item) {
return DebugSerializeMsgId(item);
}, [](v::null_t) -> QString {
return "null";
});
}
Data::FileOrigin ComputeFileOrigin(const Key &key, const Context &context) {
return v::match(key, [&](PhotoId photoId) {
return v::match(context, [&](PeerId peerId) {
@@ -474,6 +511,40 @@ void GroupThumbs::RefreshFromSlice(
}
}
template <typename Slice>
void ValidateSlice(
const Slice &slice,
const Context &context,
int from,
int index,
int till) {
auto keys = base::flat_set<Key>();
for (auto i = from; i != till; ++i) {
const auto key = ComputeKey(slice, i);
if (keys.contains(key)) {
// All items should be unique!
auto strings = QStringList();
strings.reserve(till - from);
for (auto i = from; i != till; ++i) {
strings.push_back(DebugSerializeKey(ComputeKey(slice, i)));
}
CrashReports::SetAnnotation(
"keys",
QString("%1:%2-(%3)-%4:"
).arg(DebugSerializeContext(context)
).arg(from
).arg(index
).arg(till) + strings.join(","));
if (Logs::DebugEnabled()) {
Unexpected("Bad slice in GroupThumbs.");
}
break;
} else {
keys.emplace(key);
}
}
}
template <typename Slice>
void GroupThumbs::fillItems(
const Slice &slice,
@@ -487,6 +558,8 @@ void GroupThumbs::fillItems(
const auto current = (index - from);
const auto old = base::take(_items);
//ValidateSlice(slice, _context, from, index, till);
markCacheStale();
_items.reserve(till - from);
for (auto i = from; i != till; ++i) {
@@ -514,13 +587,21 @@ void GroupThumbs::animateAliveItems(int current) {
}
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
_dying.reserve(_cache.size() - _items.size());
//Expects(_cache.size() >= _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
animatePreviouslyAlive(old);
markRestAsDying();
}
void GroupThumbs::markRestAsDying() {
_dying.reserve(_cache.size() - _items.size());
//Expects(_cache.size() >= _items.size());
if (_cache.size() >= _items.size()) {
_dying.reserve(_cache.size() - _items.size());
}
for (const auto &cacheItem : _cache) {
const auto &thumb = cacheItem.second;
const auto state = thumb->state();

View File

@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/openssl_help.h"
#include "base/qthelp_url.h"
#include "base/unixtime.h"
#include "base/platform/base_platform_info.h"
#include "zlib.h"
namespace MTP {
@@ -81,6 +82,25 @@ using namespace details;
return idsStr + "]";
}
[[nodiscard]] QString ComputeAppVersion() {
return QString::fromLatin1(AppVersionStr) + ([] {
#if defined OS_MAC_STORE
return u" Mac App Store"_q;
#elif defined OS_WIN_STORE // OS_MAC_STORE
return (Platform::IsWindows64Bit() ? u" x64"_q : QString())
+ u" Microsoft Store"_q;
#elif defined Q_OS_UNIX && !defined Q_OS_MAC // OS_MAC_STORE || OS_WIN_STORE
return Platform::InFlatpak()
? u" Flatpak"_q
: Platform::InSnap()
? u" Snap"_q
: QString();
#else // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
return Platform::IsWindows64Bit() ? u" x64"_q : QString();
#endif // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
})();
}
void WrapInvokeAfter(
SerializedRequest &to,
const SerializedRequest &from,
@@ -632,26 +652,7 @@ void SessionPrivate::tryToSend() {
const auto systemVersion = (_currentDcType == DcType::Cdn)
? "n/a"
: _instance->systemVersion();
#if defined OS_MAC_STORE
const auto appVersion = QString::fromLatin1(AppVersionStr)
+ " Mac App Store";
#elif defined OS_WIN_STORE // OS_MAC_STORE
const auto appVersion = QString::fromLatin1(AppVersionStr)
+ " Microsoft Store";
#elif defined Q_OS_UNIX && !defined Q_OS_MAC // OS_MAC_STORE || OS_WIN_STORE
const auto appVersion = [] {
if (Platform::InFlatpak()) {
return QString::fromLatin1(AppVersionStr)
+ " Flatpak";
} else if (Platform::InSnap()) {
return QString::fromLatin1(AppVersionStr)
+ " Snap";
}
return QString::fromLatin1(AppVersionStr);
}();
#else // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
const auto appVersion = QString::fromLatin1(AppVersionStr);
#endif // OS_MAC_STORE || OS_WIN_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
const auto appVersion = ComputeAppVersion();
const auto proxyType = _options->proxy.type;
const auto mtprotoProxy = (proxyType == ProxyData::Type::Mtproto);
const auto clientProxyFields = mtprotoProxy
@@ -1669,7 +1670,6 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
auto rFrom = originalRequest->constData() + 8;
const auto rEnd = originalRequest->constData() + originalRequest->size();
auto toAck = QVector<MTPlong>();
if (mtpTypeId(*rFrom) == mtpc_msgs_state_req) {
MTPMsgsStateReq request;
if (!request.read(rFrom, rEnd)) {

View File

@@ -39,7 +39,7 @@ void UnsafeOpenUrl(const QString &url) {
return;
}
QProcess::execute(qsl("xdg-open"), { url });
QProcess::startDetached(qsl("xdg-open"), { url });
}
void UnsafeOpenEmailLink(const QString &email) {
@@ -83,7 +83,7 @@ void UnsafeLaunch(const QString &filepath) {
return;
}
QProcess::execute(qsl("xdg-open"), { qUrlPath.toEncoded() });
QProcess::startDetached(qsl("xdg-open"), { qUrlPath.toEncoded() });
}
} // namespace File

View File

@@ -61,9 +61,9 @@ void Launcher::initHook() {
appimagePath.size(),
md5Hash);
return qsl("appimagekit_%1-%2.desktop")
.arg(md5Hash)
.arg(AppName.utf16().replace(' ', '_'));
return qsl("appimagekit_%1-%2.desktop").arg(
md5Hash,
AppName.utf16().replace(' ', '_'));
}
return qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME) ".desktop");

View File

@@ -147,6 +147,8 @@ private:
rpl::event_stream<> _accept;
rpl::event_stream<> _reject;
bool _destroyedConnected = false;
};
class GtkFileDialog : public QDialog {
@@ -261,8 +263,9 @@ void QGtkDialog::exec() {
}
void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) {
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); },
Qt::UniqueConnection);
if (!std::exchange(_destroyedConnected, true)) {
connect(parent, &QWindow::destroyed, this, [=] { onParentWindowDestroyed(); });
}
setParent(parent);
setFlags(flags);
setModality(modality);

View File

@@ -0,0 +1,461 @@
/*
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 "platform/linux/linux_mpris_support.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_linux_glibmm_helper.h"
#include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h"
#include "data/data_document.h"
#include "core/sandbox.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "mainwindow.h"
#include "app.h"
#include <QtGui/QGuiApplication>
#include <glibmm.h>
#include <giomm.h>
namespace Platform {
namespace internal {
namespace {
constexpr auto kService = "org.mpris.MediaPlayer2.tdesktop"_cs;
constexpr auto kObjectPath = "/org/mpris/MediaPlayer2"_cs;
constexpr auto kFakeTrackPath = "/org/telegram/desktop/track/0"_cs;
constexpr auto kInterface = "org.mpris.MediaPlayer2"_cs;
constexpr auto kPlayerInterface = "org.mpris.MediaPlayer2.Player"_cs;
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
constexpr auto kSongType = AudioMsgId::Type::Song;
constexpr auto kIntrospectionXML = R"INTROSPECTION(<node>
<interface name='org.mpris.MediaPlayer2'>
<method name='Raise'/>
<method name='Quit'/>
<property name='CanQuit' type='b' access='read'/>
<property name='CanRaise' type='b' access='read'/>
<property name='HasTrackList' type='b' access='read'/>
<property name='Identity' type='s' access='read'/>
<property name='DesktopEntry' type='s' access='read'/>
<property name='SupportedUriSchemes' type='as' access='read'/>
<property name='SupportedMimeTypes' type='as' access='read'/>
<property name='Fullscreen' type='b' access='readwrite'/>
<property name='CanSetFullscreen' type='b' access='read'/>
</interface>
</node>)INTROSPECTION"_cs;
constexpr auto kPlayerIntrospectionXML = R"INTROSPECTION(<node>
<interface name='org.mpris.MediaPlayer2.Player'>
<method name='Next'/>
<method name='Previous'/>
<method name='Pause'/>
<method name='PlayPause'/>
<method name='Stop'/>
<method name='Play'/>
<method name='Seek'>
<arg direction='in' name='Offset' type='x'/>
</method>
<method name='SetPosition'>
<arg direction='in' name='TrackId' type='o'/>
<arg direction='in' name='Position' type='x'/>
</method>
<method name='OpenUri'>
<arg direction='in' name='Uri' type='s'/>
</method>
<signal name='Seeked'>
<arg name='Position' type='x'/>
</signal>
<property name='PlaybackStatus' type='s' access='read'/>
<property name='Rate' type='d' access='readwrite'/>
<property name='Metadata' type='a{sv}' access='read'>
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QVariantMap"/>
</property>
<property name='Volume' type='d' access='readwrite'/>
<property name='Position' type='x' access='read'/>
<property name='MinimumRate' type='d' access='read'/>
<property name='MaximumRate' type='d' access='read'/>
<property name='CanGoNext' type='b' access='read'/>
<property name='CanGoPrevious' type='b' access='read'/>
<property name='CanPlay' type='b' access='read'/>
<property name='CanPause' type='b' access='read'/>
<property name='CanSeek' type='b' access='read'/>
<property name='CanControl' type='b' access='read'/>
</interface>
</node>)INTROSPECTION"_cs;
auto CreateMetadata(const Media::Player::TrackState &state) {
std::map<Glib::ustring, Glib::VariantBase> result;
if (!Media::Player::IsStoppedOrStopping(state.state)) {
result["mpris:trackid"] = Glib::wrap(g_variant_new_object_path(
kFakeTrackPath.utf8().constData()));
result["mpris:length"] = Glib::Variant<long>::create(
state.length * 1000);
const auto audioData = state.id.audio();
if (audioData) {
result["xesam:title"] = Glib::Variant<
Glib::ustring
>::create(audioData->filename().toStdString());
if (audioData->isSong()) {
const auto songData = audioData->song();
if (!songData->performer.isEmpty()) {
result["xesam:artist"] = Glib::Variant<
std::vector<Glib::ustring>
>::create({ songData->performer.toStdString() });
}
if (!songData->performer.isEmpty()) {
result["xesam:title"] = Glib::Variant<
Glib::ustring
>::create(songData->title.toStdString());
}
}
}
}
return result;
}
auto PlaybackStatus(Media::Player::State state) {
return (state == Media::Player::State::Playing)
? "Playing"
: Media::Player::IsPausedOrPausing(state)
? "Paused"
: "Stopped";
}
void HandleMethodCall(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &method_name,
const Glib::VariantContainerBase &parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
try {
auto parametersCopy = parameters;
if (method_name == "Quit") {
App::quit();
} else if (method_name == "Raise") {
App::wnd()->showFromTray();
} else if (method_name == "Next") {
Media::Player::instance()->next();
} else if (method_name == "Pause") {
Media::Player::instance()->pause();
} else if (method_name == "Play") {
Media::Player::instance()->play();
} else if (method_name == "PlayPause") {
Media::Player::instance()->playPause();
} else if (method_name == "Previous") {
Media::Player::instance()->previous();
} else if (method_name == "Seek") {
const auto offset = base::Platform::GlibVariantCast<long>(
parametersCopy.get_child(0));
const auto state = Media::Player::instance()->getState(
kSongType);
Media::Player::instance()->finishSeeking(
kSongType,
float64(state.position * 1000 + offset)
/ (state.length * 1000));
} else if (method_name == "SetPosition") {
const auto position = base::Platform::GlibVariantCast<long>(
parametersCopy.get_child(1));
const auto state = Media::Player::instance()->getState(
kSongType);
Media::Player::instance()->finishSeeking(
kSongType,
float64(position) / (state.length * 1000));
} else if (method_name == "Stop") {
Media::Player::instance()->stop();
} else {
return;
}
invocation->return_value({});
} catch (...) {
}
});
}
void HandleGetProperty(
Glib::VariantBase &property,
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &property_name) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
if (property_name == "CanQuit") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanRaise") {
property = Glib::Variant<bool>::create(!IsWayland());
} else if (property_name == "CanSetFullscreen") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "DesktopEntry") {
property = Glib::Variant<Glib::ustring>::create(
QGuiApplication::desktopFileName().chopped(8).toStdString());
} else if (property_name == "Fullscreen") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "HasTrackList") {
property = Glib::Variant<bool>::create(false);
} else if (property_name == "Identity") {
property = Glib::Variant<Glib::ustring>::create(
std::string(AppName));
} else if (property_name == "SupportedMimeTypes") {
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
} else if (property_name == "SupportedUriSchemes") {
property = Glib::Variant<std::vector<Glib::ustring>>::create({});
} else if (property_name == "CanControl") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanGoNext") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanGoPrevious") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanPause") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanPlay") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "CanSeek") {
property = Glib::Variant<bool>::create(true);
} else if (property_name == "MaximumRate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "Metadata") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = base::Platform::MakeGlibVariant(
CreateMetadata(state));
} else if (property_name == "MinimumRate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "PlaybackStatus") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = Glib::Variant<Glib::ustring>::create(
PlaybackStatus(state.state));
} else if (property_name == "Position") {
const auto state = Media::Player::instance()->getState(
kSongType);
property = Glib::Variant<long>::create(state.position * 1000);
} else if (property_name == "Rate") {
property = Glib::Variant<float64>::create(1.0);
} else if (property_name == "Volume") {
property = Glib::Variant<float64>::create(
Core::App().settings().songVolume());
}
});
}
bool HandleSetProperty(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &property_name,
const Glib::VariantBase &value) {
try {
if (property_name == "Fullscreen") {
} else if (property_name == "Rate") {
} else if (property_name == "Volume") {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
Core::App().settings().setSongVolume(
base::Platform::GlibVariantCast<float64>(value));
});
} else {
return false;
}
return true;
} catch (...) {
}
return false;
}
const Gio::DBus::InterfaceVTable InterfaceVTable(
sigc::ptr_fun(&HandleMethodCall),
sigc::ptr_fun(&HandleGetProperty),
sigc::ptr_fun(&HandleSetProperty));
void PlayerPropertyChanged(
const Glib::ustring &name,
const Glib::VariantBase &value) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
connection->emit_signal(
std::string(kObjectPath),
std::string(kPropertiesInterface),
"PropertiesChanged",
{},
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring(std::string(kPlayerInterface)),
std::map<Glib::ustring, Glib::VariantBase>{
{ name, value },
},
std::vector<Glib::ustring>{},
}));
} catch (...) {
}
}
void Seeked(long position) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
connection->emit_signal(
std::string(kObjectPath),
std::string(kPlayerInterface),
"Seeked",
{},
base::Platform::MakeGlibVariant(std::tuple{
position,
}));
} catch (...) {
}
}
} // namespace
class MPRISSupport::Private {
public:
void updateTrackState(const Media::Player::TrackState &state);
Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
Glib::RefPtr<Gio::DBus::NodeInfo> introspectionData;
Glib::RefPtr<Gio::DBus::NodeInfo> playerIntrospectionData;
uint ownId = 0;
uint registerId = 0;
uint playerRegisterId = 0;
std::map<Glib::ustring, Glib::VariantBase> metadata;
Glib::ustring playbackStatus;
long position = 0;
rpl::lifetime lifetime;
};
void MPRISSupport::Private::updateTrackState(
const Media::Player::TrackState &state) {
if (state.id.type() != kSongType) {
return;
}
const auto currentMetadata = CreateMetadata(state);
const auto currentPosition = state.position * 1000;
const auto currentPlaybackStatus = PlaybackStatus(state.state);
if (!ranges::equal(currentMetadata, metadata, [&](
const auto &item1,
const auto &item2) {
return item1.first == item2.first
&& item1.second.equal(item2.second);
})) {
metadata = currentMetadata;
PlayerPropertyChanged(
"Metadata",
Glib::Variant<
std::map<Glib::ustring, Glib::VariantBase>
>::create(metadata));
}
if (currentPlaybackStatus != playbackStatus) {
playbackStatus = currentPlaybackStatus;
PlayerPropertyChanged(
"PlaybackStatus",
Glib::Variant<Glib::ustring>::create(playbackStatus));
}
if (currentPosition != position) {
const auto positionDifference = position - currentPosition;
if (positionDifference > 1000000 || positionDifference < -1000000) {
Seeked(currentPosition);
}
position = currentPosition;
}
}
MPRISSupport::MPRISSupport()
: _private(std::make_unique<Private>()) {
try {
_private->introspectionData = Gio::DBus::NodeInfo::create_for_xml(
std::string(kIntrospectionXML));
_private->playerIntrospectionData = Gio::DBus::NodeInfo::create_for_xml(
std::string(kPlayerIntrospectionXML));
_private->ownId = Gio::DBus::own_name(
Gio::DBus::BusType::BUS_TYPE_SESSION,
std::string(kService));
_private->dbusConnection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
_private->registerId = _private->dbusConnection->register_object(
std::string(kObjectPath),
_private->introspectionData->lookup_interface(),
InterfaceVTable);
_private->playerRegisterId = _private->dbusConnection->register_object(
std::string(kObjectPath),
_private->playerIntrospectionData->lookup_interface(),
InterfaceVTable);
_private->updateTrackState(
Media::Player::instance()->getState(kSongType));
Media::Player::instance()->updatedNotifier(
) | rpl::start_with_next([=](
const Media::Player::TrackState &state) {
_private->updateTrackState(state);
}, _private->lifetime);
Core::App().settings().songVolumeChanges(
) | rpl::start_with_next([=](float64 volume) {
PlayerPropertyChanged(
"Volume",
Glib::Variant<float64>::create(volume));
}, _private->lifetime);
} catch (...) {
}
}
MPRISSupport::~MPRISSupport() {
if (_private->dbusConnection) {
if (_private->playerRegisterId) {
_private->dbusConnection->unregister_object(
_private->playerRegisterId);
}
if (_private->registerId) {
_private->dbusConnection->unregister_object(
_private->registerId);
}
}
if (_private->ownId) {
Gio::DBus::unown_name(_private->ownId);
}
}
} // namespace internal
} // namespace Platform

View File

@@ -0,0 +1,24 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Platform {
namespace internal {
class MPRISSupport {
public:
MPRISSupport();
~MPRISSupport();
private:
class Private;
const std::unique_ptr<Private> _private;
};
} // namespace internal
} // namespace Platform

View File

@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
#include "base/platform/linux/base_linux_dbus_utilities.h"
#include "platform/linux/linux_notification_service_watcher.h"
#include "platform/linux/linux_mpris_support.h"
#include "platform/linux/linux_gsd_media_keys.h"
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
@@ -71,6 +72,10 @@ constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
constexpr auto kIBusPortalService = "org.freedesktop.portal.IBus"_cs;
constexpr auto kSnapcraftSettingsService = "io.snapcraft.Settings"_cs;
constexpr auto kSnapcraftSettingsObjectPath = "/io/snapcraft/Settings"_cs;
constexpr auto kSnapcraftSettingsInterface = kSnapcraftSettingsService;
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
std::unique_ptr<internal::NotificationServiceWatcher> NSWInstance;
@@ -196,6 +201,68 @@ PortalAutostart::PortalAutostart(bool start, bool silent) {
}
}
class SnapDefaultHandler : public QWindow {
public:
SnapDefaultHandler(const QString &protocol);
};
SnapDefaultHandler::SnapDefaultHandler(const QString &protocol) {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
auto reply = connection->call_sync(
std::string(kSnapcraftSettingsObjectPath),
std::string(kSnapcraftSettingsInterface),
"GetSub",
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring("default-url-scheme-handler"),
Glib::ustring(protocol.toStdString()),
}),
std::string(kSnapcraftSettingsService));
const auto currentHandler = base::Platform::GlibVariantCast<
Glib::ustring>(reply.get_child(0));
const auto expectedHandler = qEnvironmentVariable("SNAP_NAME")
+ qsl(".desktop");
if (currentHandler == expectedHandler.toStdString()) {
return;
}
QEventLoop loop;
connection->call(
std::string(kSnapcraftSettingsObjectPath),
std::string(kSnapcraftSettingsInterface),
"SetSub",
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring("default-url-scheme-handler"),
Glib::ustring(protocol.toStdString()),
Glib::ustring(expectedHandler.toStdString()),
}),
[&](const Glib::RefPtr<Gio::AsyncResult> &result) {
try {
connection->call_finish(result);
} catch (const Glib::Error &e) {
LOG(("Snap Default Handler Error: %1").arg(
QString::fromStdString(e.what())));
}
loop.quit();
},
std::string(kSnapcraftSettingsService));
QGuiApplicationPrivate::showModalWindow(this);
loop.exec();
QGuiApplicationPrivate::hideModalWindow(this);
} catch (const Glib::Error &e) {
LOG(("Snap Default Handler Error: %1").arg(
QString::fromStdString(e.what())));
}
}
bool IsIBusPortalPresent() {
static const auto Result = [&] {
try {
@@ -347,12 +414,25 @@ void SetGtkScaleFactor() {
void SetWatchingMediaKeys(bool watching) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
static std::unique_ptr<internal::GSDMediaKeys> Instance;
static std::unique_ptr<internal::MPRISSupport> MPRISInstance;
static std::unique_ptr<internal::GSDMediaKeys> GSDInstance;
if (watching && !Instance) {
Instance = std::make_unique<internal::GSDMediaKeys>();
} else if (!watching && Instance) {
Instance = nullptr;
if (watching) {
if (!MPRISInstance) {
MPRISInstance = std::make_unique<internal::MPRISSupport>();
}
if (!GSDInstance) {
GSDInstance = std::make_unique<internal::GSDMediaKeys>();
}
} else {
if (MPRISInstance) {
MPRISInstance = nullptr;
}
if (GSDInstance) {
GSDInstance = nullptr;
}
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
@@ -465,7 +545,7 @@ std::optional<bool> IsDarkMode() {
if (!themeName.has_value()) {
return std::nullopt;
} else if (themeName->toLower().contains(qsl("-dark"))) {
} else if (themeName->contains(qsl("-dark"), Qt::CaseInsensitive)) {
return true;
}
@@ -593,6 +673,9 @@ void start() {
Glib::init();
Gio::init();
Glib::set_prgname(cExeName().toStdString());
Glib::set_application_name(std::string(AppName));
if (const auto integration = BaseGtkIntegration::Instance()) {
integration->prepareEnvironment();
} else {
@@ -671,6 +754,11 @@ void InstallLauncher(bool force) {
void RegisterCustomScheme(bool force) {
try {
if (InSnap()) {
SnapDefaultHandler(qsl("tg"));
return;
}
if (cExeName().isEmpty()) {
return;
}

View File

@@ -84,7 +84,7 @@ void GroupMembersWidget::removePeer(PeerData *selectedPeer) {
return it->second.rights;
}
}
return MTP_chatBannedRights(MTP_flags(0), MTP_int(0));
return ChannelData::EmptyRestrictedRights(user);
}();
const auto peer = this->peer();
@@ -140,7 +140,7 @@ void GroupMembersWidget::refreshUserOnline(UserData *user) {
_now = base::unixtime::now();
auto member = getMember(it.value());
auto member = getMember(it->second);
member->statusHasOnlineColor = !user->isBot()
&& Data::OnlineTextActive(user->onlineTill, _now);
member->onlineTill = user->onlineTill;
@@ -420,16 +420,16 @@ void GroupMembersWidget::setItemFlags(
auto GroupMembersWidget::computeMember(not_null<UserData*> user)
-> not_null<Member*> {
auto it = _membersByUser.constFind(user);
auto it = _membersByUser.find(user);
if (it == _membersByUser.cend()) {
auto member = new Member(user);
it = _membersByUser.insert(user, member);
it = _membersByUser.emplace(user, member).first;
member->statusHasOnlineColor = !user->isBot()
&& Data::OnlineTextActive(user->onlineTill, _now);
member->onlineTill = user->onlineTill;
member->onlineForSort = Data::SortByOnlineValue(user, _now);
}
return it.value();
return it->second;
}
void GroupMembersWidget::onUpdateOnlineDisplay() {
@@ -461,7 +461,7 @@ void GroupMembersWidget::onUpdateOnlineDisplay() {
GroupMembersWidget::~GroupMembersWidget() {
auto members = base::take(_membersByUser);
for_const (auto member, members) {
for (const auto &[_, member] : members) {
delete member;
}
}

View File

@@ -79,7 +79,7 @@ private:
not_null<ChannelData*> megagroup);
bool addUsersToEnd(not_null<ChannelData*> megagroup);
QMap<UserData*, Member*> _membersByUser;
base::flat_map<UserData*, Member*> _membersByUser;
bool _sortByOnline = false;
TimeId _now = 0;

View File

@@ -38,15 +38,15 @@ QString ToFilePart(FileKey val) {
bool KeyAlreadyUsed(QString &name) {
name += '0';
if (QFileInfo(name).exists()) {
if (QFileInfo::exists(name)) {
return true;
}
name[name.size() - 1] = '1';
if (QFileInfo(name).exists()) {
if (QFileInfo::exists(name)) {
return true;
}
name[name.size() - 1] = 's';
if (QFileInfo(name).exists()) {
if (QFileInfo::exists(name)) {
return true;
}
return false;
@@ -319,7 +319,7 @@ bool ReadFile(
// detect order of read attempts
QString toTry[2];
const auto modern = base + 's';
if (QFileInfo(modern).exists()) {
if (QFileInfo::exists(modern)) {
toTry[0] = modern;
} else {
// Legacy way.

View File

@@ -387,9 +387,9 @@ QString FormatUpdateNotification(const QString &path, const Delta &delta) {
if (!delta.changed.empty()) {
result += qstr("-------- Modified --------\n\n");
for (const auto question : delta.changed) {
result += qsl("Q: %1\nA: %2\n\n"
).arg(question->question
).arg(question->value.trimmed());
result += qsl("Q: %1\nA: %2\n\n").arg(
question->question,
question->value.trimmed());
}
}
if (!delta.removed.empty()) {
@@ -734,7 +734,7 @@ auto Templates::query(const QString &text) const -> std::vector<Question> {
pairById
) | ranges::views::filter([](const Pair &pair) {
return pair.second > 0;
}) | ranges::to_vector | ranges::action::stable_sort(sorter);
}) | ranges::to_vector | ranges::actions::stable_sort(sorter);
return good | ranges::views::transform([&](const Pair &pair) {
return questionById(pair.first);
}) | ranges::views::take(kQueryLimit) | ranges::to_vector;

View File

@@ -31,7 +31,7 @@ CountryInput::CountryInput(QWidget *parent, const style::InputField &st) : TWidg
auto availableWidth = width() - _st.textMargins.left() - _st.textMargins.right() - _st.placeholderMargins.left() - _st.placeholderMargins.right() - 1;
auto placeholderFont = _st.placeholderFont->f;
placeholderFont.setStyleStrategy(QFont::PreferMatch);
auto metrics = QFontMetrics(placeholderFont);
//auto metrics = QFontMetrics(placeholderFont);
auto placeholder = QString();// metrics.elidedText(tr::lng_country_fake_ph(tr::now), Qt::ElideRight, availableWidth);
if (!placeholder.isNull()) {
_placeholderPath.addText(0, QFontMetrics(placeholderFont).ascent(), placeholderFont, placeholder);

View File

@@ -389,7 +389,7 @@ QImage FilterIconPanel::grabForAnimation() {
_a_show = base::take(showAnimation);
_showAnimation = base::take(showAnimationData);
_a_opacity = base::take(opacityAnimation);
_cache = base::take(_cache);
_cache = base::take(cache);
return result;
}

View File

@@ -223,10 +223,12 @@ UserpicButton::UserpicButton(
, _peer(peer)
, _cropTitle(CropTitle(_peer))
, _role(role) {
Expects(_role != Role::OpenProfile);
Expects(_role != Role::OpenProfile && _role != Role::OpenPhoto);
_waiting = false;
processPeerPhoto();
prepare();
setupPeerViewers();
}
void UserpicButton::prepare() {
@@ -432,8 +434,10 @@ void UserpicButton::paintUserpicFrame(Painter &p, QPoint photoPosition) {
if (_streamed
&& _streamed->player().ready()
&& !_streamed->player().videoSize().isEmpty()) {
const auto paused = _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::RoundPlaying);
const auto paused = _controller
? _controller->isGifPausedAtLeastFor(
Window::GifPauseReason::RoundPlaying)
: false;
auto request = Media::Streaming::FrameRequest();
auto size = QSize{ _st.photoSize, _st.photoSize };
request.outer = size * cIntRetinaFactor();

View File

@@ -13,14 +13,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
void ShowMultilineToast(MultilineToastArgs &&args) {
Ui::Toast::Show(Ui::Toast::Config{
auto config = Ui::Toast::Config{
.text = std::move(args.text),
.st = &st::defaultMultilineToast,
.durationMs = (args.duration
? args.duration
: Ui::Toast::kDefaultDuration),
.multiline = true,
});
};
if (args.parentOverride) {
Ui::Toast::Show(args.parentOverride, std::move(config));
} else {
Ui::Toast::Show(std::move(config));
}
}
} // namespace Ui

View File

@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
struct MultilineToastArgs {
QWidget *parentOverride = nullptr;
TextWithEntities text;
crl::time duration = 0;
};

View File

@@ -658,8 +658,8 @@ void MainWindow::savePosition(Qt::WindowState state) {
auto centerY = realPosition.y + realPosition.h / 2;
int minDelta = 0;
QScreen *chosen = nullptr;
auto screens = QGuiApplication::screens();
for (auto screen : QGuiApplication::screens()) {
const auto screens = QGuiApplication::screens();
for (auto screen : screens) {
auto delta = (screen->geometry().center() - QPoint(centerX, centerY)).manhattanLength();
if (!chosen || delta < minDelta) {
minDelta = delta;

View File

@@ -1068,11 +1068,18 @@ void PeerMenuAddChannelMembers(
auto already = (
list
) | ranges::views::transform([](const MTPChannelParticipant &p) {
return p.match([](const auto &data) {
return data.vuser_id().v;
return p.match([](const MTPDchannelParticipantBanned &data) {
return peerFromMTP(data.vpeer());
}, [](const MTPDchannelParticipantLeft &data) {
return peerFromMTP(data.vpeer());
}, [](const auto &data) {
return peerFromUser(data.vuser_id());
});
}) | ranges::views::transform([&](UserId userId) {
return channel->owner().userLoaded(userId);
}) | ranges::views::transform([&](PeerId participantId) {
return peerIsUser(participantId)
? channel->owner().userLoaded(
peerToUser(participantId))
: nullptr;
}) | ranges::views::filter([](UserData *user) {
return (user != nullptr);
}) | ranges::to_vector;

View File

@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "data/data_group_call.h"
#include "data/data_chat_filters.h"
#include "passport/passport_form_controller.h"
#include "chat_helpers/tabbed_selector.h"
@@ -151,7 +152,9 @@ void SessionNavigation::resolveChannelById(
return;
}
const auto fail = [=] {
Ui::Toast::Show(tr::lng_error_post_link_invalid(tr::now));
Ui::ShowMultilineToast({
.text = { tr::lng_error_post_link_invalid(tr::now) }
});
};
_session->api().request(base::take(_resolveRequestId)).cancel();
_resolveRequestId = _session->api().request(MTPchannels_GetChannels(
@@ -176,17 +179,48 @@ void SessionNavigation::showPeerByLinkResolved(
not_null<PeerData*> peer,
const PeerByLinkInfo &info) {
if (info.voicechatHash && peer->isChannel()) {
const auto bad = [=] {
Ui::ShowMultilineToast({
.text = { tr::lng_group_invite_bad_link(tr::now) }
});
};
const auto hash = *info.voicechatHash;
_session->api().request(base::take(_resolveRequestId)).cancel();
_resolveRequestId = _session->api().request(
MTPchannels_GetFullChannel(peer->asChannel()->inputChannel)
).done([=](const MTPmessages_ChatFull &result) {
_session->api().processFullPeer(peer, result);
if (const auto call = peer->groupCall()) {
parentController()->startOrJoinGroupCall(peer, hash);
} else {
Ui::Toast::Show(tr::lng_error_post_link_invalid(tr::now));
const auto call = peer->groupCall();
if (!call) {
bad();
return;
}
const auto join = [=] {
parentController()->startOrJoinGroupCall(
peer,
hash,
SessionController::GroupCallJoinConfirm::Always);
};
if (call->loaded()) {
join();
return;
}
const auto id = call->id();
_resolveRequestId = _session->api().request(
MTPphone_GetGroupCall(call->input())
).done([=](const MTPphone_GroupCall &result) {
if (const auto now = peer->groupCall()
; now && now->id() == id) {
if (!now->loaded()) {
now->processFullCall(result);
}
join();
} else {
bad();
}
}).fail([=](const MTP::Error &error) {
bad();
}).send();
}).send();
return;
}
@@ -949,30 +983,32 @@ void SessionController::closeThirdSection() {
void SessionController::startOrJoinGroupCall(
not_null<PeerData*> peer,
QString joinHash,
bool confirmedLeaveOther) {
GroupCallJoinConfirm confirm) {
auto &calls = Core::App().calls();
const auto confirm = [&](QString text, QString button) {
const auto askConfirmation = [&](QString text, QString button) {
Ui::show(Box<ConfirmBox>(text, button, crl::guard(this, [=] {
Ui::hideLayer();
startOrJoinGroupCall(peer, joinHash, true);
startOrJoinGroupCall(peer, joinHash, GroupCallJoinConfirm::None);
})));
};
if (!confirmedLeaveOther && calls.inCall()) {
if (confirm != GroupCallJoinConfirm::None && calls.inCall()) {
// Do you want to leave your active voice chat
// to join a voice chat in this group?
confirm(
askConfirmation(
tr::lng_call_leave_to_other_sure(tr::now),
tr::lng_call_bar_hangup(tr::now));
} else if (!confirmedLeaveOther && calls.inGroupCall()) {
} else if (confirm != GroupCallJoinConfirm::None
&& calls.inGroupCall()) {
if (calls.currentGroupCall()->peer() == peer) {
calls.activateCurrentCall(joinHash);
} else {
confirm(
askConfirmation(
tr::lng_group_call_leave_to_other_sure(tr::now),
tr::lng_group_call_leave(tr::now));
}
} else {
calls.startOrJoinGroupCall(peer, joinHash);
const auto confirmNeeded = (confirm == GroupCallJoinConfirm::Always);
calls.startOrJoinGroupCall(peer, joinHash, confirmNeeded);
}
}

View File

@@ -298,10 +298,15 @@ public:
void resizeForThirdSection();
void closeThirdSection();
enum class GroupCallJoinConfirm {
None,
IfNowInAnother,
Always,
};
void startOrJoinGroupCall(
not_null<PeerData*> peer,
QString joinHash = QString(),
bool confirmedLeaveOther = false);
GroupCallJoinConfirm confirm = GroupCallJoinConfirm::IfNowInAnother);
void showSection(
std::shared_ptr<SectionMemento> memento,

View File

@@ -138,10 +138,17 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ]; then
echo "Copying from docker result folder."
cp "$ReleasePath/root/$BinaryName" "$ReleasePath/$BinaryName"
cp "$ReleasePath/root/$BinaryName.sym" "$ReleasePath/$BinaryName.sym"
cp "$ReleasePath/root/Updater" "$ReleasePath/Updater"
cp "$ReleasePath/root/Packer" "$ReleasePath/Packer"
echo "Dumping debug symbols.."
"$ReleasePath/dump_syms" "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
echo "Done!"
echo "Stripping the executable.."
strip -s "$ReleasePath/$BinaryName"
echo "Done!"
echo "Preparing version $AppVersionStrFull, executing Packer.."
cd "$ReleasePath"
"./Packer" -path "$BinaryName" -path Updater -version $VersionForPacker $AlphaBetaParam

View File

@@ -16,7 +16,7 @@ if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then
fi
Run () {
scl enable devtoolset-8 -- "$@"
scl enable devtoolset-9 -- "$@"
}
HomePath="$FullScriptPath/../.."
@@ -86,17 +86,8 @@ if [ "$BadCount" != "0" ]; then
Error "Bad GCC usages found: $BadCount"
fi
echo "Dumping debug symbols.."
/dump_syms "$ReleasePath/$BinaryName" > "$ReleasePath/$BinaryName.sym"
echo "Done!"
echo "Stripping the executable.."
strip -s "$ReleasePath/$BinaryName"
echo "Done!"
rm -rf "$ReleasePath/root"
mkdir "$ReleasePath/root"
mv "$ReleasePath/$BinaryName" "$ReleasePath/root/"
mv "$ReleasePath/$BinaryName.sym" "$ReleasePath/root/"
mv "$ReleasePath/Updater" "$ReleasePath/root/"
mv "$ReleasePath/Packer" "$ReleasePath/root/"

View File

@@ -19,10 +19,10 @@ RUN yum -y install git cmake3 meson ninja-build autoconf automake libtool \
freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \
pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel \
webkitgtk4-devel pkgconfig bison yasm file which xorg-x11-util-macros \
devtoolset-8-make devtoolset-8-gcc devtoolset-8-gcc-c++ \
devtoolset-8-binutils
devtoolset-9-make devtoolset-9-gcc devtoolset-9-gcc-c++ \
devtoolset-9-binutils
SHELL [ "scl", "enable", "devtoolset-8", "--", "bash", "-c" ]
SHELL [ "scl", "enable", "devtoolset-9", "--", "bash", "-c" ]
RUN ln -s cmake3 /usr/bin/cmake
ENV LibrariesPath /usr/src/Libraries
@@ -402,6 +402,7 @@ RUN make -j$(nproc)
RUN make DESTDIR="$LibrariesPath/ffmpeg-cache" install
FROM builder AS openal
ADD https://api.github.com/repos/telegramdesktop/openal-soft/git/refs/heads/fix_pulse_default openal-soft-version.json
RUN git clone -b fix_pulse_default --depth=1 $GIT/telegramdesktop/openal-soft.git
WORKDIR openal-soft
@@ -597,7 +598,7 @@ RUN git checkout bc8fb886
RUN git clone https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss
WORKDIR src/third_party/lss
RUN git checkout a91633d1
RUN git checkout 8048ece
WORKDIR ${LibrariesPath}
ENV BreakpadCache ${LibrariesPath}/breakpad-cache

View File

@@ -1,10 +1,10 @@
#!/bin/bash
cd Telegram
scl enable devtoolset-8 -- ./configure.sh "$@"
scl enable devtoolset-9 -- ./configure.sh "$@"
if [ -n "$DEBUG" ]; then
scl enable devtoolset-8 -- cmake3 --build ../out/Debug -j$(nproc)
scl enable devtoolset-9 -- cmake3 --build ../out/Debug -j$(nproc)
else
scl enable devtoolset-8 -- cmake3 --build ../out/Release -j$(nproc)
scl enable devtoolset-9 -- cmake3 --build ../out/Release -j$(nproc)
fi

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