Send / receive messages in group calls.

This commit is contained in:
John Preston
2025-09-15 17:10:27 +04:00
parent 5ed8f0baa5
commit e2ae0a7a4f
14 changed files with 432 additions and 37 deletions
+2
View File
@@ -363,6 +363,8 @@ PRIVATE
calls/group/calls_group_members_row.h
calls/group/calls_group_menu.cpp
calls/group/calls_group_menu.h
calls/group/calls_group_messages.cpp
calls/group/calls_group_messages.h
calls/group/calls_group_message_field.cpp
calls/group/calls_group_message_field.h
calls/group/calls_group_panel.cpp
-7
View File
@@ -22,13 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
namespace Api {
namespace {
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
} // namespace
Polls::Polls(not_null<ApiWrap*> api)
: _session(&api->session())
@@ -25,10 +25,6 @@ namespace {
constexpr auto kSendTogglesDelay = 3 * crl::time(1000);
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
} // namespace
TodoLists::TodoLists(not_null<ApiWrap*> api)
+3 -1
View File
@@ -2128,7 +2128,9 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updateGroupCallParticipants:
case mtpc_updateGroupCallChainBlocks:
case mtpc_updateGroupCallConnection:
case mtpc_updateGroupCall: {
case mtpc_updateGroupCall:
case mtpc_updateGroupCallMessage:
case mtpc_updateGroupCallEncryptedMessage: {
Core::App().calls().handleUpdate(&session(), update);
} break;
+14 -10
View File
@@ -107,10 +107,6 @@ using PhotoFileLocationId = Data::PhotoFileLocationId;
using DocumentFileLocationId = Data::DocumentFileLocationId;
using UpdatedFileReferences = Data::UpdatedFileReferences;
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> ShowForPeer(
not_null<PeerData*> peer) {
if (const auto window = Core::App().windowFor(peer)) {
@@ -155,6 +151,14 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
} // namespace
namespace Api {
TimeId UnixtimeFromMsgId(mtpMsgId msgId) {
return TimeId(msgId >> 32);
}
} // namespace Api
ApiWrap::ApiWrap(not_null<Main::Session*> session)
: MTP::Sender(&session->account().mtp())
, _session(session)
@@ -2182,7 +2186,7 @@ void ApiWrap::saveDraftsToCloud() {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
const auto cloudDraft = history->cloudDraft(
topicRootId,
monoforumPeerId);
@@ -2203,7 +2207,7 @@ void ApiWrap::saveDraftsToCloud() {
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
const auto cloudDraft = history->cloudDraft(
topicRootId,
monoforumPeerId);
@@ -4059,7 +4063,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
history->finishSavingCloudDraft(
draftTopicRootId,
draftMonoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
}
};
const auto fail = [=](
@@ -4074,7 +4078,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
history->finishSavingCloudDraft(
draftTopicRootId,
draftMonoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
}
};
const auto mtpShortcut = Data::ShortcutIdToMTP(
@@ -4270,7 +4274,7 @@ void ApiWrap::sendInlineResult(
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(true);
}
@@ -4279,7 +4283,7 @@ void ApiWrap::sendInlineResult(
history->finishSavingCloudDraft(
topicRootId,
monoforumPeerId,
UnixtimeFromMsgId(response.outerMsgId));
Api::UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(false);
}
+2
View File
@@ -129,6 +129,8 @@ QString RequestKey(Types &&...values) {
return result;
}
[[nodiscard]] TimeId UnixtimeFromMsgId(mtpMsgId msgId);
} // namespace Api
class ApiWrap final : public MTP::Sender {
+11 -1
View File
@@ -596,6 +596,10 @@ void Instance::handleUpdate(
handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallChainBlocks &data) {
handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallMessage &data) {
handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallEncryptedMessage &data) {
handleGroupCallUpdate(session, update);
}, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate.");
});
@@ -711,11 +715,17 @@ void Instance::handleGroupCallUpdate(
groupCall->handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDupdateGroupCallConnection &data) {
groupCall->handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDupdateGroupCallMessage &data) {
groupCall->handleIncomingMessage(data);
}, [&](const MTPDupdateGroupCallEncryptedMessage &data) {
groupCall->handleIncomingMessage(data);
}, [](const auto &) {
});
}
if (update.type() == mtpc_updateGroupCallConnection) {
if (update.type() == mtpc_updateGroupCallConnection
|| update.type() == mtpc_updateGroupCallMessage
|| update.type() == mtpc_updateGroupCallEncryptedMessage) {
return;
}
const auto callId = update.match([](const MTPDupdateGroupCall &data) {
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_messages.h"
#include "calls/calls_instance.h"
#include "main/session/session_show.h"
#include "main/main_app_config.h"
@@ -597,6 +598,7 @@ GroupCall::GroupCall(
, _peer(join.peer)
, _history(_peer->owner().history(_peer))
, _api(&_peer->session().mtp())
, _messages(std::make_unique<Group::Messages>(this, &_api))
, _joinAs(join.joinAs)
, _possibleJoinAs(std::move(join.possibleJoinAs))
, _joinHash(join.joinHash)
@@ -1710,6 +1712,8 @@ void GroupCall::startConference() {
return;
}
applyInputCall(_conferenceCall->input());
_realChanges.fire_copy(_conferenceCall.get());
initialJoinRequested();
joinDone(
TimestampInMsFromMsgId(response.outerMsgId),
@@ -2370,6 +2374,32 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
}
}
void GroupCall::handleIncomingMessage(
const MTPDupdateGroupCallMessage &data) {
const auto id = data.vcall().match([&](const MTPDinputGroupCall &data) {
return data.vid().v;
}, [](const auto &) -> CallId {
Unexpected("slug/msg in GroupCall::handleIncomingMessage");
});
if (id != _id || conference()) {
return;
}
_messages->received(data);
}
void GroupCall::handleIncomingMessage(
const MTPDupdateGroupCallEncryptedMessage &data) {
const auto id = data.vcall().match([&](const MTPDinputGroupCall &data) {
return data.vid().v;
}, [](const auto &) -> CallId {
Unexpected("slug/msg in GroupCall::handleIncomingMessage");
});
if (id != _id || !conference()) {
return;
}
_messages->received(data);
}
void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) {
if (data.vid().v == _id) {
LOG(("Call Info: Hangup after groupCallDiscarded."));
@@ -2995,9 +3025,7 @@ bool GroupCall::tryCreateController() {
});
return result;
},
.e2eEncryptDecrypt = (_e2eEncryptDecrypt
? _e2eEncryptDecrypt->callback()
: nullptr),
.e2eEncryptDecrypt = e2eEncryptDecrypt(),
};
if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + u"DebugLogs"_q;
@@ -3050,9 +3078,7 @@ bool GroupCall::tryCreateScreencast() {
.videoCapture = _screenCapture,
.videoContentType = tgcalls::VideoContentType::Screencast,
.videoCodecPreferences = lookupVideoCodecPreferences(),
.e2eEncryptDecrypt = (_e2eEncryptDecrypt
? _e2eEncryptDecrypt->callback()
: nullptr),
.e2eEncryptDecrypt = e2eEncryptDecrypt(),
};
LOG(("Call Info: Creating group screen instance"));
@@ -4095,6 +4121,17 @@ void GroupCall::setNotRequireARGB32() {
_requireARGB32 = false;
}
std::function<std::vector<uint8_t>(
std::vector<uint8_t> const &,
int64_t, bool,
int32_t)> GroupCall::e2eEncryptDecrypt() const {
return _e2eEncryptDecrypt ? _e2eEncryptDecrypt->callback() : nullptr;
}
void GroupCall::sendMessage(TextWithTags message) {
_messages->send(std::move(message));
}
auto GroupCall::otherParticipantStateValue() const
-> rpl::producer<Group::ParticipantState> {
return _otherParticipantStateValue.events();
@@ -60,6 +60,7 @@ struct RejoinEvent;
struct RtmpInfo;
enum class VideoQuality;
enum class Error;
class Messages;
} // namespace Group
struct InviteRequest;
@@ -278,6 +279,9 @@ public:
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void handlePossibleCreateOrJoinResponse(
const MTPDupdateGroupCallConnection &data);
void handleIncomingMessage(const MTPDupdateGroupCallMessage &data);
void handleIncomingMessage(
const MTPDupdateGroupCallEncryptedMessage &data);
void changeTitle(const QString &title);
void toggleRecording(
bool enabled,
@@ -446,6 +450,13 @@ public:
void pushToTalk(bool pressed, crl::time delay);
void setNotRequireARGB32();
[[nodiscard]] std::function<std::vector<uint8_t>(
std::vector<uint8_t> const &,
int64_t, bool,
int32_t)> e2eEncryptDecrypt() const;
void sendMessage(TextWithTags message);
[[nodiscard]] MTPInputGroupCall inputCall() const;
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@@ -647,7 +658,6 @@ private:
[[nodiscard]] int activeVideoSendersCount() const;
[[nodiscard]] MTPInputGroupCall inputCall() const;
[[nodiscard]] MTPInputGroupCall inputCallSafe() const;
const not_null<Delegate*> _delegate;
@@ -667,6 +677,7 @@ private:
base::flat_set<uint32> _unresolvedSsrcs;
rpl::event_stream<Error> _errors;
std::vector<Fn<void()>> _rejoinedCallbacks;
std::unique_ptr<Group::Messages> _messages;
bool _recordingStoppedByMe = false;
bool _requestedVideoChannelsUpdateScheduled = false;
@@ -7,8 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_message_field.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/controls/emoji_button.h"
#include "ui/controls/send_button.h"
#include "ui/widgets/fields/input_field.h"
@@ -22,14 +31,15 @@ namespace Calls::Group {
MessageField::MessageField(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show)
std::shared_ptr<ChatHelpers::Show> show,
PeerData *peer)
: _parent(parent)
, _show(std::move(show))
, _wrap(std::make_unique<Ui::RpWidget>(_parent)) {
createControls();
createControls(peer);
}
void MessageField::createControls() {
void MessageField::createControls(PeerData *peer) {
setupBackground();
const auto &st = st::storiesComposeControls;
@@ -44,10 +54,83 @@ void MessageField::createControls() {
_field->setDocumentMargin(4.);
_field->setAdditionalMargin(style::ConvertScale(4) - 4);
const auto show = _show;
const auto allow = [=](not_null<DocumentData*> emoji) {
return peer
? Data::AllowEmojiWithoutPremium(peer, emoji)
: show->session().premium();
};
InitMessageFieldHandlers({
.session = &show->session(),
.show = show,
.field = _field,
.customEmojiPaused = [=] {
return show->paused(ChatHelpers::PauseReason::Layer);
},
.allowPremiumEmoji = allow,
.fieldStyle = &st.files.caption,
});
Ui::Emoji::SuggestionsController::Init(
_parent,
_field,
&_show->session(),
{
.suggestCustomEmoji = true,
.allowCustomWithoutPremium = allow,
.st = &st.suggestions,
});
_send = Ui::CreateChild<Ui::SendButton>(_wrap.get(), st.send);
using Selector = ChatHelpers::TabbedSelector;
using Descriptor = ChatHelpers::TabbedPanelDescriptor;
_emojiPanel = Ui::CreateChild<ChatHelpers::TabbedPanel>(
_parent,
ChatHelpers::TabbedPanelDescriptor{
.ownedSelector = object_ptr<Selector>(
nullptr,
ChatHelpers::TabbedSelectorDescriptor{
.show = _show,
.st = st.tabbed,
.level = ChatHelpers::PauseReason::Layer,
.mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly,
.features = {
.stickersSettings = false,
.openStickerSets = false,
},
}),
});
_emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
_emojiPanel->hide();
_emojiPanel->selector()->setCurrentPeer(peer);
_emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(_field->textCursor(), data.emoji);
}, lifetime());
_emojiPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
const auto info = data.document->sticker();
if (info
&& info->setType == Data::StickersType::Emoji
&& !_show->session().premium()) {
ShowPremiumPreviewBox(
_show,
PremiumFeature::AnimatedEmoji);
} else {
Data::InsertCustomEmoji(_field, data.document);
}
}, lifetime());
_emojiToggle = Ui::CreateChild<Ui::EmojiButton>(_wrap.get(), st.emoji);
_emojiToggle->installEventFilter(_emojiPanel);
_emojiToggle->addClickHandler([=] {
_emojiPanel->toggleAnimated();
});
_width.value(
) | rpl::filter(
rpl::mappers::_1 > 0
@@ -79,6 +162,15 @@ void MessageField::createControls() {
}, _lifetime);
}
void MessageField::updateEmojiPanelGeometry() {
const auto parent = _emojiPanel->parentWidget();
const auto global = _emojiToggle->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
_emojiPanel->moveBottomRight(
local.y(),
local.x() + _emojiToggle->width() * 3);
}
void MessageField::setupBackground() {
_wrap->paintRequest() | rpl::start_with_next([=] {
const auto radius = st::historySendSize.height() / 2.;
@@ -96,6 +188,7 @@ void MessageField::resizeToWidth(int newWidth) {
if (_wrap->isHidden()) {
Ui::SendPendingMoveResizeEvents(_wrap.get());
}
updateEmojiPanelGeometry();
}
void MessageField::move(int x, int y) {
@@ -13,6 +13,7 @@ struct TextWithTags;
namespace ChatHelpers {
class Show;
class TabbedPanel;
} // namespace ChatHelpers
namespace Ui {
@@ -28,7 +29,8 @@ class MessageField final {
public:
MessageField(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show);
std::shared_ptr<ChatHelpers::Show> show,
PeerData *peer);
void resizeToWidth(int newWidth);
void move(int x, int y);
@@ -44,9 +46,10 @@ public:
[[nodiscard]] rpl::lifetime &lifetime();
private:
void createControls();
void createControls(PeerData *peer);
void setupBackground();
void shownAnimationCallback();
void updateEmojiPanelGeometry();
const not_null<QWidget*> _parent;
const std::shared_ptr<ChatHelpers::Show> _show;
@@ -55,6 +58,7 @@ private:
Ui::InputField *_field = nullptr;
Ui::SendButton *_send = nullptr;
Ui::EmojiButton *_emojiToggle = nullptr;
ChatHelpers::TabbedPanel *_emojiPanel = nullptr;
rpl::variable<int> _width;
@@ -0,0 +1,172 @@
/*
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/group/calls_group_messages.h"
#include "apiwrap.h"
#include "api/api_text_entities.h"
#include "base/unixtime.h"
#include "calls/group/calls_group_call.h"
#include "data/data_group_call.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "mtproto/sender.h"
#include "ui/ui_utility.h"
namespace Calls::Group {
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
: _call(call)
, _api(api) {
Ui::PostponeCall(_call, [=] {
_call->real(
) | rpl::start_with_next([=](not_null<Data::GroupCall*> call) {
_real = call;
if (ready()) {
sendPending();
} else {
Unexpected("Not ready call.");
}
}, _lifetime);
});
}
bool Messages::ready() const {
return _real && (!_call->conference() || _call->e2eEncryptDecrypt());
}
void Messages::send(TextWithTags text) {
if (!ready()) {
_pending.push_back(std::move(text));
return;
}
auto prepared = TextWithEntities{
text.text,
TextUtilities::ConvertTextTagsToEntities(text.tags)
};
auto serialized = MTPTextWithEntities(MTP_textWithEntities(
MTP_string(prepared.text),
Api::EntitiesToMTP(
&_real->session(),
prepared.entities,
Api::ConvertOption::SkipLocal)));
const auto id = ++_autoincrementId;
const auto from = _call->peer()->session().user();
_messages.push_back({
.id = id,
.peer = from,
.text = std::move(prepared),
});
if (!_call->conference()) {
_api->request(MTPphone_SendGroupCallMessage(
_call->inputCall(),
serialized
)).done([=](const MTPBool &, const MTP::Response &response) {
sent(id, response);
}).fail([=] {
failed(id);
}).send();
} else {
auto counter = ::tl::details::LengthCounter();
serialized.write(counter);
auto buffer = mtpBuffer();
buffer.reserve(counter.length);
serialized.write(buffer);
const auto view = bytes::make_span(buffer);
auto v = std::vector<std::uint8_t>(view.size());
bytes::copy(bytes::make_span(v), view);
const auto userId = peerToUser(from->id).bare;
const auto encrypt = _call->e2eEncryptDecrypt();
const auto encrypted = encrypt(v, int64_t(userId), true, 0);
_api->request(MTPphone_SendGroupCallEncryptedMessage(
_call->inputCall(),
MTP_bytes(bytes::make_span(encrypted))
)).done([=](const MTPBool &, const MTP::Response &response) {
sent(id, response);
}).fail([=] {
failed(id);
}).send();
}
}
void Messages::received(const MTPDupdateGroupCallMessage &data) {
if (!ready()) {
return;
}
received(data.vfrom_id(), data.vmessage());
}
void Messages::received(const MTPDupdateGroupCallEncryptedMessage &data) {
if (!ready()) {
return;
}
const auto fromId = data.vfrom_id();
const auto &bytes = data.vencrypted_message().v;
auto v = std::vector<std::uint8_t>(bytes.size());
bytes::copy(bytes::make_span(v), bytes::make_span(bytes));
const auto userId = peerToUser(peerFromMTP(fromId)).bare;
const auto decrypt = _call->e2eEncryptDecrypt();
const auto decrypted = decrypt(v, int64_t(userId), false, 0);
if (decrypted.empty() || decrypted.size() % 4 != 0) {
LOG(("API Error: Wrong decrypted message size: %1"
).arg(decrypted.size()));
return;
}
auto info = reinterpret_cast<const mtpPrime*>(decrypted.data());
auto text = MTPTextWithEntities();
if (!text.read(info, info + (decrypted.size() / 4))) {
LOG(("API Error: Can't parse decrypted message"));
return;
}
received(fromId, text);
}
void Messages::received(
const MTPPeer &from,
const MTPTextWithEntities &message) {
const auto id = ++_autoincrementId;
const auto peer = _call->peer();
_messages.push_back({
.id = id,
.date = base::unixtime::now(),
.peer = peer->owner().peer(peerFromMTP(from)),
.text = Api::ParseTextWithEntities(&peer->session(), message),
});
}
void Messages::sendPending() {
Expects(_real != nullptr);
for (auto &pending : base::take(_pending)) {
send(std::move(pending));
}
}
void Messages::sent(int id, const MTP::Response &response) {
const auto i = ranges::find(_messages, id, &Message::id);
if (i != end(_messages)) {
i->date = Api::UnixtimeFromMsgId(response.outerMsgId);
}
}
void Messages::failed(int id) {
const auto i = ranges::find(_messages, id, &Message::id);
if (i != end(_messages)) {
i->failed = true;
}
}
} // namespace Calls::Group
@@ -0,0 +1,65 @@
/*
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 Calls {
class GroupCall;
} // namespace Calls
namespace Data {
class GroupCall;
} // namespace Data
namespace MTP {
class Sender;
struct Response;
} // namespace MTP
namespace Calls::Group {
class Messages final {
public:
Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api);
void send(TextWithTags text);
void received(const MTPDupdateGroupCallMessage &data);
void received(const MTPDupdateGroupCallEncryptedMessage &data);
private:
struct Message {
int id = 0;
TimeId date = 0;
not_null<PeerData*> peer;
TextWithEntities text;
bool failed = false;
};
[[nodiscard]] bool ready() const;
void sendPending();
void received(const MTPPeer &from, const MTPTextWithEntities &message);
void sent(int id, const MTP::Response &response);
void failed(int id);
const not_null<GroupCall*> _call;
const not_null<MTP::Sender*> _api;
Data::GroupCall *_real = nullptr;
std::vector<TextWithTags> _pending;
std::vector<Message> _messages;
int _autoincrementId = 0;
rpl::lifetime _lifetime;
};
} // namespace Calls::Group
@@ -486,14 +486,18 @@ void Panel::toggleMessageTyping() {
if (_messageField) {
_messageField->toggle(typing);
} else if (typing) {
_messageField = std::make_unique<MessageField>(widget(), uiShow());
_messageField = std::make_unique<MessageField>(
widget(),
uiShow(),
_call->conference() ? nullptr : _call->peer().get());
updateButtonsGeometry();
_messageField->toggle(true);
_messageField->submitted(
) | rpl::start_with_next([=](TextWithTags text) {
//_call->sendMessage(text);
_call->sendMessage(std::move(text));
_messageField->toggle(false);
_messageTyping = false;
updateWideControlsVisibility();