diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5bd0a27e1..e34c4a29f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -263,6 +263,8 @@ PRIVATE calls/calls_box_controller.h calls/calls_call.cpp calls/calls_call.h + calls/calls_choose_join_as.cpp + calls/calls_choose_join_as.h calls/calls_group_call.cpp calls/calls_group_call.h calls/calls_group_common.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 49c755a92..0aa42c3e9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1984,6 +1984,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_mac_accessibility" = "Please allow **Accessibility** for Telegram in Privacy Settings.\n\nApp restart may be required."; "lng_group_call_mac_settings" = "Open Settings"; +"lng_group_call_start_as_header" = "Start Voice Chat as..."; +"lng_group_call_join_as_header" = "Join Voice Chat as..."; +"lng_group_call_display_as_header" = "Display me as..."; +"lng_group_call_join_as_about" = "Choose whether you want to be displayed as your personal account or as your channel."; +"lng_group_call_join_as_personal" = "personal account"; +"lng_group_call_edit_title" = "Edit voice chat title"; +"lng_group_call_switch_done" = "Members of this voice chat will now see you as **{user}**"; +"lng_group_call_edit_title_header" = "Voice chat title"; +"lng_group_call_recording_start" = "Start recording"; +"lng_group_call_recording_stop" = "Stop recording"; +"lng_group_call_recording_started" = "Voice chat recording started."; +"lng_group_call_recording_stopped" = "Voice chat recording stopped."; + "lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages."; "lng_player_message_today" = "Today at {time}"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 8063e253e..2b5ff08ad 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -260,9 +260,9 @@ void Controller::addHeaderBlock(not_null container) { const auto editLink = crl::guard(weak, [=] { EditLink(_peer, _data.current()); }); - const auto deleteLink = [=] { + const auto deleteLink = crl::guard(weak, [=] { DeleteLink(_peer, admin, link); - }; + }); const auto createMenu = [=] { auto result = base::make_unique_q(container); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 0dfade9b7..295bcd35c 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -730,8 +730,8 @@ groupCallTitleCloseIconOver: icon { }; groupCallTitle: WindowTitle(defaultWindowTitle) { height: 0px; - bg: transparent; - bgActive: transparent; + bg: groupCallBg; + bgActive: groupCallBg; fg: transparent; fgActive: transparent; minimize: IconButton(groupCallTitleButton) { diff --git a/Telegram/SourceFiles/calls/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/calls_choose_join_as.cpp new file mode 100644 index 000000000..4577d5112 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_choose_join_as.cpp @@ -0,0 +1,233 @@ +/* +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 "calls/calls_choose_join_as.h" + +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_channel.h" +#include "data/data_session.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 "boxes/peer_list_box.h" +#include "styles/style_boxes.h" + +namespace Calls { +namespace { + +using Context = ChooseJoinAsProcess::Context; + +class ListController : public PeerListController { +public: + ListController( + std::vector> list, + not_null selected); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + + [[nodiscard]] not_null selected() const; + +private: + std::unique_ptr createRow(not_null peer); + + std::vector> _list; + not_null _selected; + +}; + +ListController::ListController( + std::vector> list, + not_null selected) +: PeerListController() +, _list(std::move(list)) +, _selected(selected) { +} + +Main::Session &ListController::session() const { + return _selected->session(); +} + +std::unique_ptr ListController::createRow( + not_null peer) { + auto result = std::make_unique(peer); + if (peer->isSelf()) { + result->setCustomStatus( + tr::lng_group_call_join_as_personal(tr::now)); + } else if (const auto channel = peer->asChannel()) { + result->setCustomStatus( + (channel->isMegagroup() + ? tr::lng_chat_status_members + : tr::lng_chat_status_subscribers)( + tr::now, + lt_count, + channel->membersCount())); + } + return result; +} + +void ListController::prepare() { + delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); + for (const auto &peer : _list) { + auto row = createRow(peer); + const auto raw = row.get(); + delegate()->peerListAppendRow(std::move(row)); + if (peer == _selected) { + delegate()->peerListSetRowChecked(raw, true); + } + } + delegate()->peerListRefreshRows(); +} + +void ListController::rowClicked(not_null row) { + const auto peer = row->peer(); + if (peer == _selected) { + return; + } + const auto previous = delegate()->peerListFindRow(_selected->id); + Assert(previous != nullptr); + delegate()->peerListSetRowChecked(previous, false); + delegate()->peerListSetRowChecked(row, true); + _selected = peer; +} + +not_null ListController::selected() const { + return _selected; +} + +void ChooseJoinAsBox( + not_null box, + Context context, + std::vector> list, + not_null selected, + Fn)> done) { + 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::Switch: return tr::lng_group_call_display_as_header(); + } + Unexpected("Context in ChooseJoinAsBox."); + }()); + box->addRow(object_ptr( + box, + tr::lng_group_call_join_as_about(), + st::confirmPhoneAboutLabel)); + + auto &lifetime = box->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + const auto controller = lifetime.make_state( + std::move(list), + selected); + //controller->setStyleOverrides(); + const auto content = box->addRow( + object_ptr(box, controller), + style::margins()); + delegate->setContent(content); + controller->setDelegate(delegate); + box->addButton(tr::lng_continue(), [=] { + const auto selected = controller->selected(); + box->closeBox(); + done(selected); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +} // namespace + +ChooseJoinAsProcess::~ChooseJoinAsProcess() { + if (_request) { + _request->peer->session().api().request(_request->id).cancel(); + } +} + +void ChooseJoinAsProcess::start( + not_null peer, + Context context, + Fn, not_null)> done) { + Expects(done != nullptr); + + const auto session = &peer->session(); + if (_request) { + const auto already = _request->peer; + _request->context = context; + _request->done = std::move(done); + if (already == peer) { + return; + } else if (&already->session() == session) { + _request->peer = peer; + return; + } + session->api().request(_request->id).cancel(); + _request = nullptr; + } + _request = std::make_unique( + ChannelsListRequest{ + .peer = peer, + .done = std::move(done), + .context = context }); + session->account().sessionChanges( + ) | rpl::start_with_next([=] { + _request = nullptr; + }, _request->lifetime); + + const auto finish = [=](not_null joinAs) { + const auto peer = _request->peer; + const auto done = std::move(_request->done); + _request = nullptr; + done(peer, joinAs); + }; + using Flag = MTPchannels_GetAdminedPublicChannels::Flag; + _request->id = session->api().request( + MTPchannels_GetAdminedPublicChannels( + MTP_flags(Flag::f_for_groupcall)) + ).done([=](const MTPmessages_Chats &result) { + const auto &chats = result.match([](const auto &data) { + return data.vchats().v; + }); + const auto peer = _request->peer; + const auto self = peer->session().user(); + if (chats.size() == 1) { + finish(self); + return; + } + auto list = std::vector>(); + list.reserve(chats.size() + 1); + list.push_back(self); + for (const auto &chat : chats) { + list.push_back(session->data().processChat(chat)); + } + const auto selectedId = peer->groupCallDefaultJoinAs(); + const auto selected = [&]() -> not_null { + if (!selectedId) { + return self; + } + const auto loaded = session->data().peerLoaded(selectedId); + return (loaded && ranges::contains(list, not_null{ loaded })) + ? not_null(loaded) + : self; + }(); + Ui::show( + Box( + ChooseJoinAsBox, + _request->context, + std::move(list), + selected, + crl::guard(&_request->guard, finish)), + Ui::LayerOption::KeepOther); + }).fail([=](const RPCError &error) { + finish(session->user()); + }).send(); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_choose_join_as.h b/Telegram/SourceFiles/calls/calls_choose_join_as.h new file mode 100644 index 000000000..a131abbaa --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_choose_join_as.h @@ -0,0 +1,45 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/weak_ptr.h" + +class PeerData; + +namespace Calls { + +class ChooseJoinAsProcess final { +public: + ChooseJoinAsProcess() = default; + ~ChooseJoinAsProcess(); + + enum class Context { + Create, + Join, + Switch, + }; + + void start( + not_null peer, + Context context, + Fn peer, not_null joinAs)> done); + +private: + struct ChannelsListRequest { + not_null peer; + Fn, not_null)> done; + base::has_weak_ptr guard; + rpl::lifetime lifetime; + Context context = Context(); + mtpRequestId id = 0; + }; + std::unique_ptr _request; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 039ef0be0..32d8f5bb2 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -103,10 +103,12 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); GroupCall::GroupCall( not_null delegate, not_null peer, - const MTPInputGroupCall &inputCall) + const MTPInputGroupCall &inputCall, + not_null joinAs) : _delegate(delegate) , _peer(peer) , _history(peer->owner().history(peer)) +, _joinAs(joinAs) , _api(&peer->session().mtp()) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) @@ -244,9 +246,9 @@ void GroupCall::playConnectingSoundOnce() { void GroupCall::start() { _createRequestId = _api.request(MTPphone_CreateGroupCall( - MTP_flags(0), + MTP_flags(MTPphone_CreateGroupCall::Flag::f_join_as), _peer->input, - MTPInputPeer(), // #TODO calls join_as + _joinAs->input, MTP_int(openssl::RandomValue()) )).done([=](const MTPUpdates &result) { _acceptFields = true; @@ -351,11 +353,12 @@ void GroupCall::rejoin() { const auto wasMuteState = muted(); using Flag = MTPphone_JoinGroupCall::Flag; _api.request(MTPphone_JoinGroupCall( - MTP_flags((wasMuteState != MuteState::Active) - ? Flag::f_muted - : Flag(0)), + MTP_flags(Flag::f_join_as + | (wasMuteState != MuteState::Active + ? Flag::f_muted + : Flag(0))), inputCall(), - MTPInputPeer(), // #TODO calls join_as + _joinAs->input, MTP_dataJSON(MTP_bytes(json)) )).done([=](const MTPUpdates &updates) { _mySsrc = ssrc; diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 9cd52633b..1ed732a6c 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -89,7 +89,8 @@ public: GroupCall( not_null delegate, not_null peer, - const MTPInputGroupCall &inputCall); + const MTPInputGroupCall &inputCall, + not_null joinAs); ~GroupCall(); [[nodiscard]] uint64 id() const { @@ -207,6 +208,7 @@ private: const not_null _delegate; not_null _peer; // Can change in legacy group migration. not_null _history; // Can change in legacy group migration. + not_null _joinAs; MTP::Sender _api; rpl::variable _state = State::Creating; bool _instanceConnected = false; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index f26dbfb86..8c11f41b0 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -1346,21 +1346,26 @@ base::unique_qptr MembersController::createRowContextMenu( result->addAction( tr::lng_context_view_profile(tr::now), showProfile); - result->addAction( - tr::lng_context_send_message(tr::now), - showHistory); + if (participantPeer->isUser()) { + result->addAction( + tr::lng_context_send_message(tr::now), + showHistory); + } const auto canKick = [&] { const auto user = participantPeer->asUser(); + if (!user) { + return false; // #TODO calls can kick + } if (static_cast(row.get())->state() == Row::State::Invited) { return false; } else if (const auto chat = _peer->asChat()) { return chat->amCreator() || (user && chat->canBanMembers() - && !chat->admins.contains(user)); // #TODO calls can kick + && !chat->admins.contains(user)); } else if (const auto group = _peer->asMegagroup()) { return group->amCreator() - || (user && group->canRestrictUser(user)); // #TODO calls can kick + || (user && group->canRestrictUser(user)); } return false; }(); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index e6c78e82d..665914ab5 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -60,12 +60,26 @@ void Instance::startOutgoingCall(not_null user, bool video) { } void Instance::startOrJoinGroupCall(not_null peer) { + const auto context = peer->groupCall() + ? ChooseJoinAsProcess::Context::Join + : ChooseJoinAsProcess::Context::Create; + _chooseJoinAs.start(peer, context, [=]( + not_null peer, + not_null joinAs) { + startOrJoinGroupCall(peer, joinAs); + }); +} + +void Instance::startOrJoinGroupCall( + not_null peer, + not_null joinAs) { destroyCurrentCall(); const auto call = peer->groupCall(); createGroupCall( peer, - call ? call->input() : MTP_inputGroupCall(MTPlong(), MTPlong())); + call ? call->input() : MTP_inputGroupCall(MTPlong(), MTPlong()), + joinAs); } void Instance::callFinished(not_null call) { @@ -195,13 +209,15 @@ void Instance::destroyGroupCall(not_null call) { void Instance::createGroupCall( not_null peer, - const MTPInputGroupCall &inputCall) { + const MTPInputGroupCall &inputCall, + not_null joinAs) { destroyCurrentCall(); auto call = std::make_unique( getGroupCallDelegate(), peer, - inputCall); + inputCall, + joinAs); const auto raw = call.get(); peer->session().account().sessionChanges( diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 3aa192feb..fe7ae440b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "calls/calls_call.h" #include "calls/calls_group_call.h" +#include "calls/calls_choose_join_as.h" namespace Platform { enum class PermissionType; @@ -41,6 +42,9 @@ public: void startOutgoingCall(not_null user, bool video); void startOrJoinGroupCall(not_null peer); + void startOrJoinGroupCall( + not_null peer, + not_null joinAs); void handleUpdate( not_null session, const MTPUpdate &update); @@ -104,7 +108,8 @@ private: void createGroupCall( not_null peer, - const MTPInputGroupCall &inputCall); + const MTPInputGroupCall &inputCall, + not_null joinAs); void destroyGroupCall(not_null call); void requestPermissionOrFail( @@ -145,6 +150,8 @@ private: base::flat_map> _tracks; + ChooseJoinAsProcess _chooseJoinAs; + }; } // namespace Calls diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index c09430116..f6d0454ea 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -713,6 +713,14 @@ void ChannelData::clearGroupCall() { | MTPDchannel::Flag::f_call_not_empty); } +void ChannelData::setGroupCallDefaultJoinAs(PeerId peerId) { + _callDefaultJoinAs = peerId; +} + +PeerId ChannelData::groupCallDefaultJoinAs() const { + return _callDefaultJoinAs; +} + namespace Data { void ApplyMigration( @@ -759,6 +767,11 @@ void ApplyChannelUpdate( } else { channel->clearGroupCall(); } + if (const auto as = update.vgroupcall_default_join_as()) { + channel->setGroupCallDefaultJoinAs(peerFromMTP(*as)); + } else { + channel->setGroupCallDefaultJoinAs(0); + } channel->setMessagesTTL(update.vttl_period().value_or_empty()); channel->setFullFlags(update.vflags().v); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index cf0c98dc7..0cb9e88a7 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -410,6 +410,8 @@ public: void migrateCall(std::unique_ptr call); void setGroupCall(const MTPInputGroupCall &call); void clearGroupCall(); + void setGroupCallDefaultJoinAs(PeerId peerId); + [[nodiscard]] PeerId groupCallDefaultJoinAs() const; // Still public data members. uint64 access = 0; @@ -458,6 +460,7 @@ private: std::optional _linkedChat; std::unique_ptr _call; + PeerId _callDefaultJoinAs = 0; int _slowmodeSeconds = 0; TimeId _slowmodeLastMessage = 0; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 2da1d3c4a..91ee91313 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -237,6 +237,14 @@ void ChatData::clearGroupCall() { | MTPDchat::Flag::f_call_not_empty); } +void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) { + _callDefaultJoinAs = peerId; +} + +PeerId ChatData::groupCallDefaultJoinAs() const { + return _callDefaultJoinAs; +} + namespace Data { void ApplyChatUpdate( @@ -376,6 +384,11 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { } else { chat->clearGroupCall(); } + if (const auto as = update.vgroupcall_default_join_as()) { + chat->setGroupCallDefaultJoinAs(peerFromMTP(*as)); + } else { + chat->setGroupCallDefaultJoinAs(0); + } chat->setMessagesTTL(update.vttl_period().value_or_empty()); if (const auto info = update.vbot_info()) { diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index b6ef3e5a0..595a50542 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -170,6 +170,8 @@ public: } void setGroupCall(const MTPInputGroupCall &call); void clearGroupCall(); + void setGroupCallDefaultJoinAs(PeerId peerId); + [[nodiscard]] PeerId groupCallDefaultJoinAs() const; // Still public data members. const MTPint inputChat; @@ -195,6 +197,7 @@ private: int _version = 0; std::unique_ptr _call; + PeerId _callDefaultJoinAs = 0; ChannelData *_migratedTo = nullptr; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 6a7d33849..ec9ccb379 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -920,6 +920,15 @@ Data::GroupCall *PeerData::groupCall() const { return nullptr; } +PeerId PeerData::groupCallDefaultJoinAs() const { + if (const auto chat = asChat()) { + return chat->groupCallDefaultJoinAs(); + } else if (const auto group = asMegagroup()) { + return group->groupCallDefaultJoinAs(); + } + return 0; +} + void PeerData::setIsBlocked(bool is) { const auto status = is ? BlockStatus::Blocked diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 43921e8f6..7c7670a29 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -386,6 +386,7 @@ public: void setMessagesTTL(TimeId period); [[nodiscard]] Data::GroupCall *groupCall() const; + [[nodiscard]] PeerId groupCallDefaultJoinAs() const; const PeerId id; QString name; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 48e65c590..3d642a79e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -949,7 +949,8 @@ void SessionController::startOrJoinGroupCall( }))); }; if (!confirmedLeaveOther && calls.inCall()) { - // Do you want to leave your active voice chat to join a voice chat in this group? + // Do you want to leave your active voice chat + // to join a voice chat in this group? confirm( tr::lng_call_leave_to_other_sure(tr::now), tr::lng_call_bar_hangup(tr::now)); @@ -961,10 +962,6 @@ void SessionController::startOrJoinGroupCall( tr::lng_group_call_leave_to_other_sure(tr::now), tr::lng_group_call_leave(tr::now)); } - } else if (!confirmedLeaveOther && !peer->groupCall()) { - confirm( - tr::lng_group_call_create_sure(tr::now), - tr::lng_continue(tr::now)); } else { calls.startOrJoinGroupCall(peer); }