Implement audio/video confcall invitations.

This commit is contained in:
John Preston 2025-04-01 16:09:23 +05:00
parent 021115b463
commit c5531b1bd8
15 changed files with 293 additions and 47 deletions

View file

@ -4642,6 +4642,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_call_status_failed" = "failed to connect"; "lng_call_status_failed" = "failed to connect";
"lng_call_status_ringing" = "ringing..."; "lng_call_status_ringing" = "ringing...";
"lng_call_status_busy" = "line busy"; "lng_call_status_busy" = "line busy";
"lng_call_status_group_invite" = "Telegram Group Call";
"lng_call_status_sure" = "Click on the Camera icon if you want to start a video call."; "lng_call_status_sure" = "Click on the Camera icon if you want to start a video call.";
"lng_call_fingerprint_tooltip" = "If the emoji on {user}'s screen are the same, this call is 100% secure"; "lng_call_fingerprint_tooltip" = "If the emoji on {user}'s screen are the same, this call is 100% secure";

View file

@ -1543,6 +1543,26 @@ confcallJoinUserpics: UserpicsRow {
invert: true; invert: true;
} }
confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px); confcallJoinUserpicsPadding: margins(0px, 0px, 0px, 16px);
confcallInviteVideo: IconButton {
width: 36px;
height: 52px;
icon: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }};
iconOver: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }};
iconPosition: point(-1px, -1px);
ripple: groupCallRipple;
rippleAreaPosition: point(0px, 8px);
rippleAreaSize: 36px;
}
confcallInviteVideoActive: icon {{ "info/info_media_video", groupCallActiveFg }};
confcallInviteVideoMargins: margins(0px, 0px, 10px, 0px);
confcallInviteAudio: IconButton(confcallInviteVideo) {
icon: icon {{ "menu/phone", groupCallMemberInactiveIcon }};
iconOver: icon {{ "menu/phone", groupCallMemberInactiveIcon }};
}
confcallInviteAudioActive: icon {{ "menu/phone", groupCallActiveFg }};
confcallInviteAudioMargins: margins(0px, 0px, 4px, 0px);
groupCallLinkBox: Box(confcallLinkBox) { groupCallLinkBox: Box(confcallLinkBox) {
bg: groupCallMembersBg; bg: groupCallMembersBg;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "base/random.h" #include "base/random.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "calls/group/calls_group_common.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "calls/calls_panel.h" #include "calls/calls_panel.h"
#include "core/application.h" #include "core/application.h"
@ -251,7 +252,8 @@ Call::Call(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
not_null<UserData*> user, not_null<UserData*> user,
CallId conferenceId, CallId conferenceId,
MsgId conferenceInviteMsgId) MsgId conferenceInviteMsgId,
bool video)
: _delegate(delegate) : _delegate(delegate)
, _user(user) , _user(user)
, _api(&_user->session().mtp()) , _api(&_user->session().mtp())
@ -279,10 +281,10 @@ Call::Call(
, _conferenceInviteMsgId(conferenceInviteMsgId) , _conferenceInviteMsgId(conferenceInviteMsgId)
, _videoIncoming( , _videoIncoming(
std::make_unique<Webrtc::VideoTrack>( std::make_unique<Webrtc::VideoTrack>(
StartVideoState(false))) StartVideoState(video)))
, _videoOutgoing( , _videoOutgoing(
std::make_unique<Webrtc::VideoTrack>( std::make_unique<Webrtc::VideoTrack>(
StartVideoState(false))) { StartVideoState(video))) {
startWaitingTrack(); startWaitingTrack();
setupOutgoingVideo(); setupOutgoingVideo();
} }

View file

@ -106,7 +106,8 @@ public:
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
not_null<UserData*> user, not_null<UserData*> user,
CallId conferenceId, CallId conferenceId,
MsgId conferenceInviteMsgId); MsgId conferenceInviteMsgId,
bool video);
[[nodiscard]] Type type() const { [[nodiscard]] Type type() const {
return _type; return _type;

View file

@ -1010,6 +1010,7 @@ void Instance::showConferenceInvite(
const auto media = item ? item->media() : nullptr; const auto media = item ? item->media() : nullptr;
const auto call = media ? media->call() : nullptr; const auto call = media ? media->call() : nullptr;
const auto conferenceId = call ? call->conferenceId : 0; const auto conferenceId = call ? call->conferenceId : 0;
const auto video = call->video;
if (!conferenceId if (!conferenceId
|| call->state != Data::CallState::Invitation || call->state != Data::CallState::Invitation
|| user->isSelf()) { || user->isSelf()) {
@ -1036,7 +1037,8 @@ void Instance::showConferenceInvite(
delegate, delegate,
user, user,
conferenceId, conferenceId,
conferenceInviteMsgId); conferenceInviteMsgId,
video);
const auto raw = call.get(); const auto raw = call.get();
user->session().account().sessionChanges( user->session().account().sessionChanges(

View file

@ -56,6 +56,7 @@ enum class CallType;
class GroupCall; class GroupCall;
class Panel; class Panel;
struct DhConfig; struct DhConfig;
struct InviteRequest;
struct StartGroupCallArgs { struct StartGroupCallArgs {
enum class JoinConfirm { enum class JoinConfirm {
@ -73,7 +74,7 @@ struct StartConferenceCallArgs {
std::shared_ptr<TdE2E::Call> e2e; std::shared_ptr<TdE2E::Call> e2e;
QString linkSlug; QString linkSlug;
MsgId joinMessageId; MsgId joinMessageId;
std::vector<not_null<UserData*>> invite; std::vector<InviteRequest> invite;
bool migrating = false; bool migrating = false;
}; };

View file

@ -467,7 +467,7 @@ void Panel::initControls() {
*creating = false; *creating = false;
} }
}; };
const auto create = [=](std::vector<not_null<UserData*>> users) { const auto create = [=](std::vector<InviteRequest> users) {
if (*creating) { if (*creating) {
return; return;
} }
@ -481,7 +481,7 @@ void Panel::initControls() {
}); });
}; };
const auto invite = crl::guard(call, [=]( const auto invite = crl::guard(call, [=](
std::vector<not_null<UserData*>> users) { std::vector<InviteRequest> users) {
create(std::move(users)); create(std::move(users));
}); });
const auto share = crl::guard(call, [=] { const auto share = crl::guard(call, [=] {
@ -1285,7 +1285,9 @@ void Panel::paint(QRect clip) {
bool Panel::handleClose() const { bool Panel::handleClose() const {
if (_call) { if (_call) {
if (_call->state() == Call::State::WaitingUserConfirmation if (_call->state() == Call::State::WaitingUserConfirmation
|| _call->state() == Call::State::Busy) { || _call->state() == Call::State::Busy
|| _call->state() == Call::State::Starting
|| _call->state() == Call::State::WaitingIncoming) {
_call->hangup(); _call->hangup();
} else { } else {
window()->hide(); window()->hide();
@ -1424,7 +1426,10 @@ void Panel::updateStatusText(State state) {
case State::ExchangingKeys: return tr::lng_call_status_exchanging(tr::now); case State::ExchangingKeys: return tr::lng_call_status_exchanging(tr::now);
case State::Waiting: return tr::lng_call_status_waiting(tr::now); case State::Waiting: return tr::lng_call_status_waiting(tr::now);
case State::Requesting: return tr::lng_call_status_requesting(tr::now); case State::Requesting: return tr::lng_call_status_requesting(tr::now);
case State::WaitingIncoming: return tr::lng_call_status_incoming(tr::now); case State::WaitingIncoming:
return (_call->conferenceInvite()
? tr::lng_call_status_group_invite(tr::now)
: tr::lng_call_status_incoming(tr::now));
case State::Ringing: return tr::lng_call_status_ringing(tr::now); case State::Ringing: return tr::lng_call_status_ringing(tr::now);
case State::Busy: return tr::lng_call_status_busy(tr::now); case State::Busy: return tr::lng_call_status_busy(tr::now);
case State::WaitingUserConfirmation: return tr::lng_call_status_sure(tr::now); case State::WaitingUserConfirmation: return tr::lng_call_status_sure(tr::now);

View file

@ -3712,7 +3712,7 @@ void GroupCall::editParticipant(
} }
void GroupCall::inviteUsers( void GroupCall::inviteUsers(
const std::vector<not_null<UserData*>> &users, const std::vector<InviteRequest> &requests,
Fn<void(InviteResult)> done) { Fn<void(InviteResult)> done) {
const auto real = lookupReal(); const auto real = lookupReal();
if (!real) { if (!real) {
@ -3737,9 +3737,11 @@ void GroupCall::inviteUsers(
}; };
if (const auto call = _conferenceCall.get()) { if (const auto call = _conferenceCall.get()) {
for (const auto &user : users) { for (const auto &request : requests) {
using Flag = MTPphone_InviteConferenceCallParticipant::Flag;
const auto user = request.user;
_api.request(MTPphone_InviteConferenceCallParticipant( _api.request(MTPphone_InviteConferenceCallParticipant(
MTP_flags(0), MTP_flags(request.video ? Flag::f_video : Flag()),
inputCallSafe(), inputCallSafe(),
user->inputUser user->inputUser
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
@ -3785,7 +3787,8 @@ void GroupCall::inviteUsers(
slice.clear(); slice.clear();
usersSlice.clear(); usersSlice.clear();
}; };
for (const auto &user : users) { for (const auto &request : requests) {
const auto user = request.user;
owner->registerInvitedToCallUser(_id, _peer, user); owner->registerInvitedToCallUser(_id, _peer, user);
usersSlice.push_back(user); usersSlice.push_back(user);
slice.push_back(user->inputUser); slice.push_back(user->inputUser);
@ -3920,7 +3923,7 @@ void GroupCall::destroyScreencast() {
} }
TextWithEntities ComposeInviteResultToast( TextWithEntities ComposeInviteResultToast(
const GroupCall::InviteResult &result) { const InviteResult &result) {
auto text = TextWithEntities(); auto text = TextWithEntities();
const auto invited = int(result.invited.size()); const auto invited = int(result.invited.size());
const auto restricted = int(result.privacyRestricted.size()); const auto restricted = int(result.privacyRestricted.size());

View file

@ -60,6 +60,9 @@ enum class VideoQuality;
enum class Error; enum class Error;
} // namespace Group } // namespace Group
struct InviteRequest;
struct InviteResult;
enum class MuteState { enum class MuteState {
Active, Active,
PushToTalk, PushToTalk,
@ -418,13 +421,8 @@ public:
void toggleMute(const Group::MuteRequest &data); void toggleMute(const Group::MuteRequest &data);
void changeVolume(const Group::VolumeRequest &data); void changeVolume(const Group::VolumeRequest &data);
struct InviteResult {
std::vector<not_null<UserData*>> invited;
std::vector<not_null<UserData*>> alreadyIn;
std::vector<not_null<UserData*>> privacyRestricted;
};
void inviteUsers( void inviteUsers(
const std::vector<not_null<UserData*>> &users, const std::vector<InviteRequest> &requests,
Fn<void(InviteResult)> done); Fn<void(InviteResult)> done);
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager(); std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
@ -754,6 +752,6 @@ private:
}; };
[[nodiscard]] TextWithEntities ComposeInviteResultToast( [[nodiscard]] TextWithEntities ComposeInviteResultToast(
const GroupCall::InviteResult &result); const InviteResult &result);
} // namespace Calls } // namespace Calls

View file

@ -41,6 +41,20 @@ namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
namespace Calls {
struct InviteRequest {
not_null<UserData*> user;
bool video = false;
};
struct InviteResult {
std::vector<not_null<UserData*>> invited;
std::vector<not_null<UserData*>> alreadyIn;
std::vector<not_null<UserData*>> privacyRestricted;
};
} // namespace Calls
namespace Calls::Group { namespace Calls::Group {
constexpr auto kDefaultVolume = 10000; constexpr auto kDefaultVolume = 10000;
@ -146,7 +160,7 @@ struct ConferenceCallLinkArgs {
bool joining = false; bool joining = false;
bool migrating = false; bool migrating = false;
Fn<void(QString)> finished; Fn<void(QString)> finished;
std::vector<not_null<UserData*>> invite; std::vector<InviteRequest> invite;
ConferenceCallLinkStyleOverrides st; ConferenceCallLinkStyleOverrides st;
}; };
void ShowConferenceCallLinkBox( void ShowConferenceCallLinkBox(
@ -163,7 +177,7 @@ void ExportConferenceCallLink(
struct ConferenceFactoryArgs { struct ConferenceFactoryArgs {
std::shared_ptr<Main::SessionShow> show; std::shared_ptr<Main::SessionShow> show;
Fn<void(QString)> finished; Fn<void(QString)> finished;
std::vector<not_null<UserData*>> invite; std::vector<InviteRequest> invite;
bool joining = false; bool joining = false;
bool migrating = false; bool migrating = false;
}; };

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "calls/group/calls_group_call.h" #include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_menu.h" #include "calls/group/calls_group_menu.h"
#include "calls/calls_call.h" #include "calls/calls_call.h"
#include "boxes/peer_lists_box.h" #include "boxes/peer_lists_box.h"
@ -19,10 +20,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/session/session_show.h" #include "main/session/session_show.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/painter.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "styles/style_boxes.h" // membersMarginTop #include "styles/style_boxes.h" // membersMarginTop
@ -61,6 +64,36 @@ namespace {
return result; return result;
} }
class ConfInviteRow final : public PeerListRow {
public:
using PeerListRow::PeerListRow;
void setAlreadyIn(bool alreadyIn);
void setVideo(bool video);
int elementsCount() const override;
QRect elementGeometry(int element, int outerWidth) const override;
bool elementDisabled(int element) const override;
bool elementOnlySelect(int element) const override;
void elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) override;
void elementsStopLastRipple() override;
void elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) override;
private:
std::unique_ptr<Ui::RippleAnimation> _videoRipple;
std::unique_ptr<Ui::RippleAnimation> _audioRipple;
bool _alreadyIn = false;
bool _video = false;
};
class ConfInviteController final : public ContactsBoxController { class ConfInviteController final : public ContactsBoxController {
public: public:
ConfInviteController( ConfInviteController(
@ -69,6 +102,8 @@ public:
Fn<void()> shareLink); Fn<void()> shareLink);
[[nodiscard]] rpl::producer<bool> hasSelectedValue() const; [[nodiscard]] rpl::producer<bool> hasSelectedValue() const;
[[nodiscard]] std::vector<InviteRequest> requests(
const std::vector<not_null<PeerData*>> &peers) const;
protected: protected:
void prepareViewHook() override; void prepareViewHook() override;
@ -77,16 +112,132 @@ protected:
not_null<UserData*> user) override; not_null<UserData*> user) override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void rowElementClicked(not_null<PeerListRow*> row, int element) override;
private: private:
[[nodiscard]] int fullCount() const; [[nodiscard]] int fullCount() const;
void toggleRowSelected(not_null<PeerListRow*> row, bool video);
const base::flat_set<not_null<UserData*>> _alreadyIn; const base::flat_set<not_null<UserData*>> _alreadyIn;
const Fn<void()> _shareLink; const Fn<void()> _shareLink;
rpl::variable<bool> _hasSelected; rpl::variable<bool> _hasSelected;
base::flat_set<not_null<UserData*>> _withVideo;
bool _lastSelectWithVideo = false;
}; };
void ConfInviteRow::setAlreadyIn(bool alreadyIn) {
_alreadyIn = alreadyIn;
setDisabledState(alreadyIn ? State::DisabledChecked : State::Active);
}
void ConfInviteRow::setVideo(bool video) {
_video = video;
}
int ConfInviteRow::elementsCount() const {
return _alreadyIn ? 0 : 2;
}
QRect ConfInviteRow::elementGeometry(int element, int outerWidth) const {
if (_alreadyIn || (element != 1 && element != 2)) {
return QRect();
}
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
const auto size = QSize(st.width, st.height);
const auto margins = (element == 1)
? st::confcallInviteVideoMargins
: st::confcallInviteAudioMargins;
const auto right = margins.right();
const auto top = margins.top();
const auto side = (element == 1)
? outerWidth
: elementGeometry(1, outerWidth).x();
const auto left = side - right - size.width();
return QRect(QPoint(left, top), size);
}
bool ConfInviteRow::elementDisabled(int element) const {
return _alreadyIn
|| (checked()
&& ((_video && element == 1) || (!_video && element == 2)));
}
bool ConfInviteRow::elementOnlySelect(int element) const {
return false;
}
void ConfInviteRow::elementAddRipple(
int element,
QPoint point,
Fn<void()> updateCallback) {
if (_alreadyIn || (element != 1 && element != 2)) {
return;
}
auto &ripple = (element == 1) ? _videoRipple : _audioRipple;
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
if (!ripple) {
auto mask = Ui::RippleAnimation::EllipseMask(QSize(
st.rippleAreaSize,
st.rippleAreaSize));
ripple = std::make_unique<Ui::RippleAnimation>(
st.ripple,
std::move(mask),
std::move(updateCallback));
}
ripple->add(point - st.rippleAreaPosition);
}
void ConfInviteRow::elementsStopLastRipple() {
if (_videoRipple) {
_videoRipple->lastStop();
}
if (_audioRipple) {
_audioRipple->lastStop();
}
}
void ConfInviteRow::elementsPaint(
Painter &p,
int outerWidth,
bool selected,
int selectedElement) {
if (_alreadyIn) {
return;
}
const auto paintElement = [&](int element) {
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
auto &ripple = (element == 1) ? _videoRipple : _audioRipple;
const auto active = checked() && ((element == 1) ? _video : !_video);
const auto geometry = elementGeometry(element, outerWidth);
if (ripple) {
ripple->paint(
p,
geometry.x() + st.rippleAreaPosition.x(),
geometry.y() + st.rippleAreaPosition.y(),
outerWidth);
if (ripple->empty()) {
ripple.reset();
}
}
const auto selected = (element == selectedElement);
const auto &icon = active
? (element == 1
? st::confcallInviteVideoActive
: st::confcallInviteAudioActive)
: (selected ? st.iconOver : st.icon);
icon.paintInCenter(p, geometry);
};
paintElement(1);
paintElement(2);
}
ConfInviteController::ConfInviteController( ConfInviteController::ConfInviteController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
base::flat_set<not_null<UserData*>> alreadyIn, base::flat_set<not_null<UserData*>> alreadyIn,
@ -100,6 +251,18 @@ rpl::producer<bool> ConfInviteController::hasSelectedValue() const {
return _hasSelected.value(); return _hasSelected.value();
} }
std::vector<InviteRequest> ConfInviteController::requests(
const std::vector<not_null<PeerData*>> &peers) const {
auto result = std::vector<InviteRequest>();
result.reserve(peers.size());
for (const auto &peer : peers) {
if (const auto user = peer->asUser()) {
result.push_back({ user, _withVideo.contains(user) });
}
}
return result;
}
std::unique_ptr<PeerListRow> ConfInviteController::createRow( std::unique_ptr<PeerListRow> ConfInviteController::createRow(
not_null<UserData*> user) { not_null<UserData*> user) {
if (user->isSelf() if (user->isSelf()
@ -108,9 +271,12 @@ std::unique_ptr<PeerListRow> ConfInviteController::createRow(
|| user->isInaccessible()) { || user->isInaccessible()) {
return nullptr; return nullptr;
} }
auto result = ContactsBoxController::createRow(user); auto result = std::make_unique<ConfInviteRow>(user);
if (_alreadyIn.contains(user)) { if (_alreadyIn.contains(user)) {
result->setDisabledState(PeerListRow::State::DisabledChecked); result->setAlreadyIn(true);
}
if (_withVideo.contains(user)) {
result->setVideo(true);
} }
return result; return result;
} }
@ -120,10 +286,42 @@ int ConfInviteController::fullCount() const {
} }
void ConfInviteController::rowClicked(not_null<PeerListRow*> row) { void ConfInviteController::rowClicked(not_null<PeerListRow*> row) {
toggleRowSelected(row, _lastSelectWithVideo);
}
void ConfInviteController::rowElementClicked(
not_null<PeerListRow*> row,
int element) {
if (row->checked()) {
static_cast<ConfInviteRow*>(row.get())->setVideo(element == 1);
_lastSelectWithVideo = (element == 1);
} else if (element == 1) {
toggleRowSelected(row, true);
} else if (element == 2) {
toggleRowSelected(row, false);
}
}
void ConfInviteController::toggleRowSelected(
not_null<PeerListRow*> row,
bool video) {
auto count = fullCount(); auto count = fullCount();
auto limit = Data::kMaxConferenceMembers; auto limit = Data::kMaxConferenceMembers;
if (count < limit || row->checked()) { if (count < limit || row->checked()) {
const auto real = static_cast<ConfInviteRow*>(row.get());
if (!row->checked()) {
real->setVideo(video);
_lastSelectWithVideo = video;
}
const auto user = row->peer()->asUser();
if (!row->checked() && video) {
_withVideo.emplace(user);
} else {
_withVideo.remove(user);
}
delegate()->peerListSetRowChecked(row, !row->checked()); delegate()->peerListSetRowChecked(row, !row->checked());
// row may have been destroyed here, from search.
_hasSelected = (delegate()->peerListSelectedRowsCount() > 0); _hasSelected = (delegate()->peerListSelectedRowsCount() > 0);
} else { } else {
delegate()->peerListUiShow()->showToast( delegate()->peerListUiShow()->showToast(
@ -311,13 +509,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
if (!call) { if (!call) {
return; return;
} }
auto peers = box->collectSelectedRows(); const auto done = [=](InviteResult result) {
auto users = ranges::views::all(
peers
) | ranges::views::transform([](not_null<PeerData*> peer) {
return not_null(peer->asUser());
}) | ranges::to_vector;
const auto done = [=](GroupCall::InviteResult result) {
(*close)(); (*close)();
if (result.invited.empty() if (result.invited.empty()
&& result.privacyRestricted.empty()) { && result.privacyRestricted.empty()) {
@ -325,7 +517,9 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
} }
showToast({ ComposeInviteResultToast(result) }); showToast({ ComposeInviteResultToast(result) });
}; };
call->inviteUsers(users, done); call->inviteUsers(
raw->requests(box->collectSelectedRows()),
done);
}); });
} }
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
@ -354,7 +548,12 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
if (!call) { if (!call) {
return; return;
} }
call->inviteUsers(users, [=](GroupCall::InviteResult result) { auto requests = ranges::views::all(
users
) | ranges::views::transform([](not_null<UserData*> user) {
return InviteRequest{ user };
}) | ranges::to_vector;
call->inviteUsers(std::move(requests), [=](InviteResult result) {
if (result.invited.size() == 1) { if (result.invited.size() == 1) {
showToast(tr::lng_group_call_invite_done_user( showToast(tr::lng_group_call_invite_done_user(
tr::now, tr::now,
@ -463,7 +662,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
object_ptr<Ui::BoxContent> PrepareInviteBox( object_ptr<Ui::BoxContent> PrepareInviteBox(
not_null<Call*> call, not_null<Call*> call,
Fn<void(std::vector<not_null<UserData*>>)> inviteUsers, Fn<void(std::vector<InviteRequest>)> inviteUsers,
Fn<void()> shareLink) { Fn<void()> shareLink) {
const auto user = call->user(); const auto user = call->user();
const auto weak = base::make_weak(call); const auto weak = base::make_weak(call);
@ -486,13 +685,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
if (!call) { if (!call) {
return; return;
} }
auto peers = box->collectSelectedRows(); inviteUsers(raw->requests(box->collectSelectedRows()));
auto users = ranges::views::all(
peers
) | ranges::views::transform([](not_null<PeerData*> peer) {
return not_null(peer->asUser());
}) | ranges::to_vector;
inviteUsers(std::move(users));
}); });
} }
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Calls { namespace Calls {
class Call; class Call;
class GroupCall; class GroupCall;
struct InviteRequest;
} // namespace Calls } // namespace Calls
namespace Calls::Group { namespace Calls::Group {
@ -83,7 +84,7 @@ private:
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteBox( [[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteBox(
not_null<Call*> call, not_null<Call*> call,
Fn<void(std::vector<not_null<UserData*>>)> inviteUsers, Fn<void(std::vector<InviteRequest>)> inviteUsers,
Fn<void()> shareLink); Fn<void()> shareLink);
} // namespace Calls::Group } // namespace Calls::Group

View file

@ -1006,8 +1006,8 @@ void Panel::migrationShowShareLink() {
{ .st = DarkConferenceCallLinkStyle() }); { .st = DarkConferenceCallLinkStyle() });
} }
void Panel::migrationInviteUsers(std::vector<not_null<UserData*>> users) { void Panel::migrationInviteUsers(std::vector<InviteRequest> users) {
const auto done = [=](GroupCall::InviteResult result) { const auto done = [=](InviteResult result) {
showToast({ ComposeInviteResultToast(result) }); showToast({ ComposeInviteResultToast(result) });
}; };
_call->inviteUsers(std::move(users), crl::guard(this, done)); _call->inviteUsers(std::move(users), crl::guard(this, done));

View file

@ -73,6 +73,10 @@ struct CallSignalBars;
struct CallBodyLayout; struct CallBodyLayout;
} // namespace style } // namespace style
namespace Calls {
struct InviteRequest;
} // namespace Calls
namespace Calls::Group { namespace Calls::Group {
class Toasts; class Toasts;
@ -116,7 +120,7 @@ public:
[[nodiscard]] bool isLayerShown() const; [[nodiscard]] bool isLayerShown() const;
void migrationShowShareLink(); void migrationShowShareLink();
void migrationInviteUsers(std::vector<not_null<UserData*>> users); void migrationInviteUsers(std::vector<InviteRequest> users);
void minimize(); void minimize();
void toggleFullScreen(); void toggleFullScreen();

View file

@ -491,6 +491,7 @@ Call ComputeCallData(const MTPDmessageActionConferenceCall &call) {
: call.is_active() : call.is_active()
? CallState::Active ? CallState::Active
: CallState::Invitation), : CallState::Invitation),
.video = call.is_video(),
}; };
} }