mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 07:33:52 +02:00
Implement multi-inviting to confcalls.
This commit is contained in:
parent
6451e1cfe2
commit
9dbd134601
15 changed files with 366 additions and 125 deletions
|
@ -4765,6 +4765,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}». Add them to the group?";
|
"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}». Add them to the group?";
|
||||||
"lng_group_call_invite_members" = "Group members";
|
"lng_group_call_invite_members" = "Group members";
|
||||||
"lng_group_call_invite_search_results" = "Search results";
|
"lng_group_call_invite_search_results" = "Search results";
|
||||||
|
"lng_group_call_invite_limit" = "This is currently the maximum allowed number of participants.";
|
||||||
"lng_group_call_new_muted" = "Mute new participants";
|
"lng_group_call_new_muted" = "Mute new participants";
|
||||||
"lng_group_call_speakers" = "Speakers";
|
"lng_group_call_speakers" = "Speakers";
|
||||||
"lng_group_call_microphone" = "Microphone";
|
"lng_group_call_microphone" = "Microphone";
|
||||||
|
@ -4921,6 +4922,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_confcall_link_or" = "or";
|
"lng_confcall_link_or" = "or";
|
||||||
"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}";
|
"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}";
|
||||||
"lng_confcall_link_join_link" = "Open call {arrow}";
|
"lng_confcall_link_join_link" = "Open call {arrow}";
|
||||||
|
"lng_confcall_invite_done_user" = "You invited {user} to join the call.";
|
||||||
|
"lng_confcall_invite_done_many#one" = "You invited **{count} person** to join the call.";
|
||||||
|
"lng_confcall_invite_done_many#other" = "You invited **{count} people** to join the call.";
|
||||||
|
"lng_confcall_invite_fail_user" = "You cannot call {user} because of their privacy settings.";
|
||||||
|
"lng_confcall_invite_fail_many#one" = "You cannot call **{count} person** because of their privacy settings.";
|
||||||
|
"lng_confcall_invite_fail_many#other" = "You cannot call **{count} people** because of their privacy settings.";
|
||||||
|
|
||||||
"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";
|
"lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";
|
||||||
|
|
||||||
|
|
|
@ -1538,3 +1538,20 @@ groupCallLinkPreview: InputField(defaultInputField) {
|
||||||
style: defaultTextStyle;
|
style: defaultTextStyle;
|
||||||
heightMin: 35px;
|
heightMin: 35px;
|
||||||
}
|
}
|
||||||
|
groupCallInviteLink: SettingsButton(defaultSettingsButton) {
|
||||||
|
textFg: mediaviewTextLinkFg;
|
||||||
|
textFgOver: mediaviewTextLinkFg;
|
||||||
|
textBg: groupCallMembersBg;
|
||||||
|
textBgOver: groupCallMembersBgOver;
|
||||||
|
|
||||||
|
style: TextStyle(defaultTextStyle) {
|
||||||
|
font: font(14px semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
height: 20px;
|
||||||
|
padding: margins(63px, 8px, 8px, 9px);
|
||||||
|
|
||||||
|
ripple: groupCallRipple;
|
||||||
|
}
|
||||||
|
groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLinkFg }};
|
||||||
|
groupCallInviteLinkIconPosition: point(23px, 2px);
|
||||||
|
|
|
@ -3697,61 +3697,88 @@ void GroupCall::editParticipant(
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
void GroupCall::inviteUsers(
|
||||||
const std::vector<not_null<UserData*>> &users) {
|
const std::vector<not_null<UserData*>> &users,
|
||||||
|
Fn<void(InviteResult)> done) {
|
||||||
const auto real = lookupReal();
|
const auto real = lookupReal();
|
||||||
if (!real) {
|
if (!real) {
|
||||||
return 0;
|
if (done) {
|
||||||
|
done({});
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const auto owner = &_peer->owner();
|
const auto owner = &_peer->owner();
|
||||||
|
|
||||||
if (_conferenceCall) {
|
struct State {
|
||||||
|
InviteResult result;
|
||||||
|
int requests = 0;
|
||||||
|
};
|
||||||
|
const auto state = std::make_shared<State>();
|
||||||
|
const auto finishRequest = [=] {
|
||||||
|
if (!--state->requests) {
|
||||||
|
if (done) {
|
||||||
|
done(std::move(state->result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const auto call = _conferenceCall.get()) {
|
||||||
for (const auto &user : users) {
|
for (const auto &user : users) {
|
||||||
_api.request(MTPphone_InviteConferenceCallParticipant(
|
_api.request(MTPphone_InviteConferenceCallParticipant(
|
||||||
inputCallSafe(),
|
inputCallSafe(),
|
||||||
user->inputUser
|
user->inputUser
|
||||||
)).send();
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
owner->registerInvitedToCallUser(_id, call, user);
|
||||||
|
_peer->session().api().applyUpdates(result);
|
||||||
|
state->result.invited.push_back(user);
|
||||||
|
finishRequest();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
const auto type = error.type();
|
||||||
|
if (type == u"USER_PRIVACY_RESTRICTED"_q) {
|
||||||
|
state->result.privacyRestricted.push_back(user);
|
||||||
|
} else if (type == u"USER_ALREADY_PARTICIPANT"_q) {
|
||||||
|
state->result.alreadyIn.push_back(user);
|
||||||
}
|
}
|
||||||
auto result = std::variant<int, not_null<UserData*>>(0);
|
finishRequest();
|
||||||
if (users.size() != 1) {
|
}).send();
|
||||||
result = int(users.size());
|
++state->requests;
|
||||||
} else {
|
|
||||||
result = users.front();
|
|
||||||
}
|
}
|
||||||
return result;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto count = 0;
|
auto usersSlice = std::vector<not_null<UserData*>>();
|
||||||
|
usersSlice.reserve(kMaxInvitePerSlice);
|
||||||
auto slice = QVector<MTPInputUser>();
|
auto slice = QVector<MTPInputUser>();
|
||||||
auto result = std::variant<int, not_null<UserData*>>(0);
|
|
||||||
slice.reserve(kMaxInvitePerSlice);
|
slice.reserve(kMaxInvitePerSlice);
|
||||||
const auto sendSlice = [&] {
|
const auto sendSlice = [&] {
|
||||||
count += slice.size();
|
|
||||||
_api.request(MTPphone_InviteToGroupCall(
|
_api.request(MTPphone_InviteToGroupCall(
|
||||||
inputCall(),
|
inputCall(),
|
||||||
MTP_vector<MTPInputUser>(slice)
|
MTP_vector<MTPInputUser>(slice)
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
_peer->session().api().applyUpdates(result);
|
_peer->session().api().applyUpdates(result);
|
||||||
|
for (const auto &user : usersSlice) {
|
||||||
|
state->result.invited.push_back(user);
|
||||||
|
}
|
||||||
|
finishRequest();
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
finishRequest();
|
||||||
}).send();
|
}).send();
|
||||||
|
++state->requests;
|
||||||
|
|
||||||
slice.clear();
|
slice.clear();
|
||||||
|
usersSlice.clear();
|
||||||
};
|
};
|
||||||
for (const auto &user : users) {
|
for (const auto &user : users) {
|
||||||
if (!count && slice.empty()) {
|
|
||||||
result = user;
|
|
||||||
}
|
|
||||||
owner->registerInvitedToCallUser(_id, _peer, user);
|
owner->registerInvitedToCallUser(_id, _peer, user);
|
||||||
|
usersSlice.push_back(user);
|
||||||
slice.push_back(user->inputUser);
|
slice.push_back(user->inputUser);
|
||||||
if (slice.size() == kMaxInvitePerSlice) {
|
if (slice.size() == kMaxInvitePerSlice) {
|
||||||
sendSlice();
|
sendSlice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count != 0 || slice.size() != 1) {
|
|
||||||
result = int(count + slice.size());
|
|
||||||
}
|
|
||||||
if (!slice.empty()) {
|
if (!slice.empty()) {
|
||||||
sendSlice();
|
sendSlice();
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto GroupCall::ensureGlobalShortcutManager()
|
auto GroupCall::ensureGlobalShortcutManager()
|
||||||
|
|
|
@ -416,8 +416,15 @@ 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);
|
||||||
std::variant<int, not_null<UserData*>> inviteUsers(
|
|
||||||
const std::vector<not_null<UserData*>> &users);
|
struct InviteResult {
|
||||||
|
std::vector<not_null<UserData*>> invited;
|
||||||
|
std::vector<not_null<UserData*>> alreadyIn;
|
||||||
|
std::vector<not_null<UserData*>> privacyRestricted;
|
||||||
|
};
|
||||||
|
void inviteUsers(
|
||||||
|
const std::vector<not_null<UserData*>> &users,
|
||||||
|
Fn<void(InviteResult)> done);
|
||||||
|
|
||||||
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
|
std::shared_ptr<GlobalShortcutManager> ensureGlobalShortcutManager();
|
||||||
void applyGlobalShortcutChanges();
|
void applyGlobalShortcutChanges();
|
||||||
|
|
|
@ -15,12 +15,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.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 "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/labels.h"
|
#include "ui/widgets/labels.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_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_dialogs.h" // searchedBarHeight
|
#include "styles/style_dialogs.h" // searchedBarHeight
|
||||||
|
|
||||||
|
@ -61,15 +65,43 @@ public:
|
||||||
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,
|
||||||
Fn<void(not_null<PeerData*>)> choose)
|
Fn<void()> shareLink);
|
||||||
: ContactsBoxController(session)
|
|
||||||
, _alreadyIn(std::move(alreadyIn))
|
[[nodiscard]] rpl::producer<bool> hasSelectedValue() const;
|
||||||
, _choose(std::move(choose)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void prepareViewHook() override;
|
||||||
|
|
||||||
std::unique_ptr<PeerListRow> createRow(
|
std::unique_ptr<PeerListRow> createRow(
|
||||||
not_null<UserData*> user) override {
|
not_null<UserData*> user) override;
|
||||||
|
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] int fullCount() const;
|
||||||
|
|
||||||
|
base::flat_set<not_null<UserData*>> _alreadyIn;
|
||||||
|
const Fn<void()> _shareLink;
|
||||||
|
rpl::variable<bool> _hasSelected;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfInviteController::ConfInviteController(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||||
|
Fn<void()> shareLink)
|
||||||
|
: ContactsBoxController(session)
|
||||||
|
, _alreadyIn(std::move(alreadyIn))
|
||||||
|
, _shareLink(std::move(shareLink)) {
|
||||||
|
_alreadyIn.remove(session->user());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> ConfInviteController::hasSelectedValue() const {
|
||||||
|
return _hasSelected.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListRow> ConfInviteController::createRow(
|
||||||
|
not_null<UserData*> user) {
|
||||||
if (user->isSelf()
|
if (user->isSelf()
|
||||||
|| user->isBot()
|
|| user->isBot()
|
||||||
|| user->isServiceUser()
|
|| user->isServiceUser()
|
||||||
|
@ -83,15 +115,88 @@ protected:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rowClicked(not_null<PeerListRow*> row) override {
|
int ConfInviteController::fullCount() const {
|
||||||
_choose(row->peer());
|
return _alreadyIn.size() + delegate()->peerListSelectedRowsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
void ConfInviteController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
const base::flat_set<not_null<UserData*>> _alreadyIn;
|
auto count = fullCount();
|
||||||
const Fn<void(not_null<PeerData*>)> _choose;
|
auto limit = Data::kMaxConferenceMembers;
|
||||||
|
if (count < limit || row->checked()) {
|
||||||
|
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||||
|
_hasSelected = (delegate()->peerListSelectedRowsCount() > 0);
|
||||||
|
} else {
|
||||||
|
delegate()->peerListUiShow()->showToast(
|
||||||
|
tr::lng_group_call_invite_limit(tr::now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
void ConfInviteController::prepareViewHook() {
|
||||||
|
auto button = object_ptr<Ui::PaddingWrap<Ui::SettingsButton>>(
|
||||||
|
nullptr,
|
||||||
|
object_ptr<Ui::SettingsButton>(
|
||||||
|
nullptr,
|
||||||
|
tr::lng_profile_add_via_link(),
|
||||||
|
st::groupCallInviteLink),
|
||||||
|
style::margins(0, st::membersMarginTop, 0, 0));
|
||||||
|
|
||||||
|
const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
|
||||||
|
button->entity(),
|
||||||
|
st::groupCallInviteLinkIcon,
|
||||||
|
QPoint());
|
||||||
|
button->entity()->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
icon->moveToLeft(
|
||||||
|
st::groupCallInviteLinkIconPosition.x(),
|
||||||
|
(height - st::groupCallInviteLinkIcon.height()) / 2);
|
||||||
|
}, icon->lifetime());
|
||||||
|
|
||||||
|
button->entity()->setClickedCallback(_shareLink);
|
||||||
|
button->entity()->events(
|
||||||
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
return (e->type() == QEvent::Enter);
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
delegate()->peerListMouseLeftGeometry();
|
||||||
|
}, button->lifetime());
|
||||||
|
delegate()->peerListSetAboveWidget(std::move(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] TextWithEntities ComposeInviteResultToast(
|
||||||
|
const GroupCall::InviteResult &result) {
|
||||||
|
auto text = TextWithEntities();
|
||||||
|
const auto invited = int(result.invited.size());
|
||||||
|
const auto restricted = int(result.privacyRestricted.size());
|
||||||
|
if (invited == 1) {
|
||||||
|
text.append(tr::lng_confcall_invite_done_user(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Bold(result.invited.front()->shortName()),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else if (invited > 1) {
|
||||||
|
text.append(tr::lng_confcall_invite_done_many(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
invited,
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
}
|
||||||
|
if (invited && restricted) {
|
||||||
|
text.append(u"\n\n"_q);
|
||||||
|
}
|
||||||
|
if (restricted == 1) {
|
||||||
|
text.append(tr::lng_confcall_invite_fail_user(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
Ui::Text::Bold(result.privacyRestricted.front()->shortName()),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else if (restricted > 1) {
|
||||||
|
text.append(tr::lng_confcall_invite_fail_many(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
restricted,
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -204,7 +309,8 @@ std::unique_ptr<PeerListRow> InviteContactsController::createRow(
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> PrepareInviteBox(
|
object_ptr<Ui::BoxContent> PrepareInviteBox(
|
||||||
not_null<GroupCall*> call,
|
not_null<GroupCall*> call,
|
||||||
Fn<void(TextWithEntities&&)> showToast) {
|
Fn<void(TextWithEntities&&)> showToast,
|
||||||
|
Fn<void(Fn<void(bool)> finished)> shareConferenceLink) {
|
||||||
const auto real = call->lookupReal();
|
const auto real = call->lookupReal();
|
||||||
if (!real) {
|
if (!real) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -220,33 +326,44 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
|
||||||
alreadyIn.emplace(peer->session().user());
|
alreadyIn.emplace(peer->session().user());
|
||||||
if (call->conference()) {
|
if (call->conference()) {
|
||||||
const auto close = std::make_shared<Fn<void()>>();
|
const auto close = std::make_shared<Fn<void()>>();
|
||||||
const auto invite = [=](not_null<PeerData*> peer) {
|
const auto shareLink = [=] {
|
||||||
Expects(peer->isUser());
|
Assert(shareConferenceLink != nullptr);
|
||||||
|
shareConferenceLink([=](bool ok) { if (ok) (*close)(); });
|
||||||
const auto call = weak.get();
|
|
||||||
if (!call) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto user = peer->asUser();
|
|
||||||
call->inviteUsers({ user });
|
|
||||||
showToast(tr::lng_group_call_invite_done_user(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
Ui::Text::Bold(user->firstName),
|
|
||||||
Ui::Text::WithEntities));
|
|
||||||
(*close)();
|
|
||||||
};
|
};
|
||||||
auto controller = std::make_unique<ConfInviteController>(
|
auto controller = std::make_unique<ConfInviteController>(
|
||||||
&real->session(),
|
&real->session(),
|
||||||
alreadyIn,
|
alreadyIn,
|
||||||
invite);
|
shareLink);
|
||||||
controller->setStyleOverrides(
|
const auto raw = controller.get();
|
||||||
|
raw->setStyleOverrides(
|
||||||
&st::groupCallInviteMembersList,
|
&st::groupCallInviteMembersList,
|
||||||
&st::groupCallMultiSelect);
|
&st::groupCallMultiSelect);
|
||||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||||
box->setTitle(tr::lng_group_call_invite_title());
|
box->setTitle(tr::lng_group_call_invite_conf());
|
||||||
|
raw->hasSelectedValue() | rpl::start_with_next([=](bool has) {
|
||||||
|
box->clearButtons();
|
||||||
|
if (has) {
|
||||||
|
box->addButton(tr::lng_group_call_invite_button(), [=] {
|
||||||
|
const auto call = weak.get();
|
||||||
|
if (!call) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto peers = box->collectSelectedRows();
|
||||||
|
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)();
|
||||||
|
showToast({ ComposeInviteResultToast(result) });
|
||||||
|
};
|
||||||
|
call->inviteUsers(users, done);
|
||||||
|
});
|
||||||
|
}
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
*close = [=] { box->closeBox(); };
|
}, box->lifetime());
|
||||||
|
*close = crl::guard(box, [=] { box->closeBox(); });
|
||||||
};
|
};
|
||||||
return Box<PeerListBox>(std::move(controller), initBox);
|
return Box<PeerListBox>(std::move(controller), initBox);
|
||||||
}
|
}
|
||||||
|
@ -270,24 +387,23 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
|
||||||
if (!call) {
|
if (!call) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto result = call->inviteUsers(users);
|
call->inviteUsers(users, [=](GroupCall::InviteResult result) {
|
||||||
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
|
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,
|
||||||
lt_user,
|
lt_user,
|
||||||
Ui::Text::Bold((*user)->firstName),
|
Ui::Text::Bold(result.invited.front()->firstName),
|
||||||
Ui::Text::WithEntities));
|
Ui::Text::WithEntities));
|
||||||
} else if (const auto count = std::get_if<int>(&result)) {
|
} else if (result.invited.size() > 1) {
|
||||||
if (*count > 0) {
|
|
||||||
showToast(tr::lng_group_call_invite_done_many(
|
showToast(tr::lng_group_call_invite_done_many(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_count,
|
lt_count,
|
||||||
*count,
|
result.invited.size(),
|
||||||
Ui::Text::RichLangValue));
|
Ui::Text::RichLangValue));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Result in GroupCall::inviteUsers.");
|
Unexpected("Result in GroupCall::inviteUsers.");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const auto inviteWithAdd = [=](
|
const auto inviteWithAdd = [=](
|
||||||
std::shared_ptr<Ui::Show> show,
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
|
|
@ -77,6 +77,7 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteBox(
|
||||||
not_null<GroupCall*> call,
|
not_null<GroupCall*> call,
|
||||||
Fn<void(TextWithEntities&&)> showToast);
|
Fn<void(TextWithEntities&&)> showToast,
|
||||||
|
Fn<void(Fn<void(bool)> finished)> shareConferenceLink = nullptr);
|
||||||
|
|
||||||
} // namespace Calls::Group
|
} // namespace Calls::Group
|
||||||
|
|
|
@ -1742,13 +1742,15 @@ void Members::setMode(PanelMode mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect Members::getInnerGeometry() const {
|
QRect Members::getInnerGeometry() const {
|
||||||
|
const auto shareLink = _shareLinkButton.current();
|
||||||
const auto addMembers = _addMemberButton.current();
|
const auto addMembers = _addMemberButton.current();
|
||||||
|
const auto share = shareLink ? shareLink->height() : 0;
|
||||||
const auto add = addMembers ? addMembers->height() : 0;
|
const auto add = addMembers ? addMembers->height() : 0;
|
||||||
return QRect(
|
return QRect(
|
||||||
0,
|
0,
|
||||||
-_scroll->scrollTop(),
|
-_scroll->scrollTop(),
|
||||||
width(),
|
width(),
|
||||||
_list->y() + _list->height() + _bottomSkip->height() + add);
|
_list->y() + _list->height() + _bottomSkip->height() + add + share);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<int> Members::fullCountValue() const {
|
rpl::producer<int> Members::fullCountValue() const {
|
||||||
|
@ -1923,16 +1925,22 @@ void Members::setupFakeRoundCorners() {
|
||||||
const auto bottomleft = create({ 0, shift });
|
const auto bottomleft = create({ 0, shift });
|
||||||
const auto bottomright = create({ shift, shift });
|
const auto bottomright = create({ shift, shift });
|
||||||
|
|
||||||
rpl::combine(
|
const auto heightValue = [=](Ui::RpWidget *widget) {
|
||||||
_list->geometryValue(),
|
|
||||||
_addMemberButton.value() | rpl::map([=](Ui::RpWidget *widget) {
|
|
||||||
topleft->raise();
|
topleft->raise();
|
||||||
topright->raise();
|
topright->raise();
|
||||||
bottomleft->raise();
|
bottomleft->raise();
|
||||||
bottomright->raise();
|
bottomright->raise();
|
||||||
return widget ? widget->heightValue() : rpl::single(0);
|
return widget ? widget->heightValue() : rpl::single(0);
|
||||||
}) | rpl::flatten_latest()
|
};
|
||||||
) | rpl::start_with_next([=](QRect list, int addMembers) {
|
rpl::combine(
|
||||||
|
_list->geometryValue(),
|
||||||
|
_addMemberButton.value() | rpl::map(
|
||||||
|
heightValue
|
||||||
|
) | rpl::flatten_latest(),
|
||||||
|
_shareLinkButton.value() | rpl::map(
|
||||||
|
heightValue
|
||||||
|
) | rpl::flatten_latest()
|
||||||
|
) | rpl::start_with_next([=](QRect list, int addMembers, int shareLink) {
|
||||||
const auto left = list.x();
|
const auto left = list.x();
|
||||||
const auto top = list.y() - _topSkip->height();
|
const auto top = list.y() - _topSkip->height();
|
||||||
const auto right = left + list.width() - topright->width();
|
const auto right = left + list.width() - topright->width();
|
||||||
|
@ -1941,6 +1949,7 @@ void Members::setupFakeRoundCorners() {
|
||||||
+ list.height()
|
+ list.height()
|
||||||
+ _bottomSkip->height()
|
+ _bottomSkip->height()
|
||||||
+ addMembers
|
+ addMembers
|
||||||
|
+ shareLink
|
||||||
- bottomleft->height();
|
- bottomleft->height();
|
||||||
topleft->move(left, top);
|
topleft->move(left, top);
|
||||||
topright->move(right, top);
|
topright->move(right, top);
|
||||||
|
|
|
@ -949,7 +949,9 @@ void Panel::setupMembers() {
|
||||||
|
|
||||||
_members->addMembersRequests(
|
_members->addMembersRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
if (!_peer->isBroadcast()
|
if (_call->conference()) {
|
||||||
|
addMembers();
|
||||||
|
} else if (!_peer->isBroadcast()
|
||||||
&& Data::CanSend(_peer, ChatRestriction::SendOther, false)
|
&& Data::CanSend(_peer, ChatRestriction::SendOther, false)
|
||||||
&& _call->joinAs()->isSelf()) {
|
&& _call->joinAs()->isSelf()) {
|
||||||
addMembers();
|
addMembers();
|
||||||
|
@ -960,19 +962,9 @@ void Panel::setupMembers() {
|
||||||
}
|
}
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
const auto exporting = std::make_shared<bool>();
|
|
||||||
_members->shareLinkRequests(
|
_members->shareLinkRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([cb = shareConferenceLinkCallback()] {
|
||||||
Expects(_call->conference());
|
cb(nullptr);
|
||||||
|
|
||||||
if (*exporting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*exporting = true;
|
|
||||||
ExportConferenceCallLink(uiShow(), _call->conferenceCall(), {
|
|
||||||
.st = DarkConferenceCallLinkStyle(),
|
|
||||||
.finished = [=](bool) { *exporting = false; },
|
|
||||||
});
|
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
_call->videoEndpointLargeValue(
|
_call->videoEndpointLargeValue(
|
||||||
|
@ -984,6 +976,28 @@ void Panel::setupMembers() {
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Fn<void(Fn<void(bool)> finished)> Panel::shareConferenceLinkCallback() {
|
||||||
|
const auto exporting = std::make_shared<bool>();
|
||||||
|
return [=](Fn<void(bool)> finished) {
|
||||||
|
Expects(_call->conference());
|
||||||
|
|
||||||
|
if (*exporting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*exporting = true;
|
||||||
|
const auto done = [=](bool ok) {
|
||||||
|
*exporting = false;
|
||||||
|
if (const auto onstack = finished) {
|
||||||
|
onstack(ok);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ExportConferenceCallLink(uiShow(), _call->conferenceCall(), {
|
||||||
|
.finished = done,
|
||||||
|
.st = DarkConferenceCallLinkStyle(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void Panel::enlargeVideo() {
|
void Panel::enlargeVideo() {
|
||||||
_lastSmallGeometry = window()->geometry();
|
_lastSmallGeometry = window()->geometry();
|
||||||
|
|
||||||
|
@ -1536,10 +1550,17 @@ void Panel::showMainMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::addMembers() {
|
void Panel::addMembers() {
|
||||||
|
if (_call->conference()
|
||||||
|
&& _call->conferenceCall()->fullCount() >= Data::kMaxConferenceMembers) {
|
||||||
|
showToast({ tr::lng_group_call_invite_limit(tr::now) });
|
||||||
|
}
|
||||||
const auto showToastCallback = [=](TextWithEntities &&text) {
|
const auto showToastCallback = [=](TextWithEntities &&text) {
|
||||||
showToast(std::move(text));
|
showToast(std::move(text));
|
||||||
};
|
};
|
||||||
if (auto box = PrepareInviteBox(_call, showToastCallback)) {
|
const auto link = _call->conference()
|
||||||
|
? shareConferenceLinkCallback()
|
||||||
|
: nullptr;
|
||||||
|
if (auto box = PrepareInviteBox(_call, showToastCallback, link)) {
|
||||||
showBox(std::move(box));
|
showBox(std::move(box));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,6 +193,8 @@ private:
|
||||||
void toggleWideControls(bool shown);
|
void toggleWideControls(bool shown);
|
||||||
void updateWideControlsVisibility();
|
void updateWideControlsVisibility();
|
||||||
[[nodiscard]] bool videoButtonInNarrowMode() const;
|
[[nodiscard]] bool videoButtonInNarrowMode() const;
|
||||||
|
[[nodiscard]] auto shareConferenceLinkCallback()
|
||||||
|
-> Fn<void(Fn<void(bool)> finished)>;
|
||||||
|
|
||||||
void endCall();
|
void endCall();
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ constexpr auto kSpeakingAfterActive = crl::time(6000);
|
||||||
constexpr auto kActiveAfterJoined = crl::time(1000);
|
constexpr auto kActiveAfterJoined = crl::time(1000);
|
||||||
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
|
constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000);
|
||||||
constexpr auto kReloadStaleTimeout = 16 * crl::time(1000);
|
constexpr auto kReloadStaleTimeout = 16 * crl::time(1000);
|
||||||
constexpr auto kMaxConferenceMembers = 50;
|
|
||||||
|
|
||||||
[[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) {
|
[[nodiscard]] QString ExtractNextOffset(const MTPphone_GroupCall &call) {
|
||||||
return call.match([&](const MTPDphone_groupCall &data) {
|
return call.match([&](const MTPDphone_groupCall &data) {
|
||||||
|
@ -245,7 +244,7 @@ void GroupCall::checkStaleRequest() {
|
||||||
MTP_vector<MTPInputPeer>(), // ids
|
MTP_vector<MTPInputPeer>(), // ids
|
||||||
MTP_vector<MTPint>(), // ssrcs
|
MTP_vector<MTPint>(), // ssrcs
|
||||||
MTP_string(QString()),
|
MTP_string(QString()),
|
||||||
MTP_int(kMaxConferenceMembers)
|
MTP_int(kMaxConferenceMembers + 10)
|
||||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||||
_checkStaleRequestId = 0;
|
_checkStaleRequestId = 0;
|
||||||
const auto &list = _participantsWithAccess.current();
|
const auto &list = _participantsWithAccess.current();
|
||||||
|
|
|
@ -30,6 +30,8 @@ namespace Data {
|
||||||
|
|
||||||
[[nodiscard]] const std::string &RtmpEndpointId();
|
[[nodiscard]] const std::string &RtmpEndpointId();
|
||||||
|
|
||||||
|
inline constexpr auto kMaxConferenceMembers = 10;
|
||||||
|
|
||||||
struct LastSpokeTimes {
|
struct LastSpokeTimes {
|
||||||
crl::time anything = 0;
|
crl::time anything = 0;
|
||||||
crl::time voice = 0;
|
crl::time voice = 0;
|
||||||
|
|
|
@ -1135,6 +1135,30 @@ GroupCall *Session::groupCall(CallId callId) const {
|
||||||
return (i != end(_groupCalls)) ? i->second.get() : nullptr;
|
return (i != end(_groupCalls)) ? i->second.get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<GroupCall> Session::sharedConferenceCall(
|
||||||
|
CallId id,
|
||||||
|
uint64 accessHash) {
|
||||||
|
const auto i = _conferenceCalls.find(id);
|
||||||
|
if (i != end(_conferenceCalls)) {
|
||||||
|
if (auto result = i->second.lock()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto result = std::make_shared<GroupCall>(
|
||||||
|
session().user(),
|
||||||
|
id,
|
||||||
|
accessHash,
|
||||||
|
TimeId(), // scheduledDate
|
||||||
|
false, // rtmp
|
||||||
|
true); // conference
|
||||||
|
if (i != end(_conferenceCalls)) {
|
||||||
|
i->second = result;
|
||||||
|
} else {
|
||||||
|
_conferenceCalls.emplace(id, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Session::watchForOffline(not_null<UserData*> user, TimeId now) {
|
void Session::watchForOffline(not_null<UserData*> user, TimeId now) {
|
||||||
if (!now) {
|
if (!now) {
|
||||||
now = base::unixtime::now();
|
now = base::unixtime::now();
|
||||||
|
@ -1205,7 +1229,13 @@ void Session::registerInvitedToCallUser(
|
||||||
CallId callId,
|
CallId callId,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<UserData*> user) {
|
not_null<UserData*> user) {
|
||||||
const auto call = peer->groupCall();
|
registerInvitedToCallUser(callId, peer->groupCall(), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::registerInvitedToCallUser(
|
||||||
|
CallId callId,
|
||||||
|
GroupCall *call,
|
||||||
|
not_null<UserData*> user) {
|
||||||
if (call && call->id() == callId) {
|
if (call && call->id() == callId) {
|
||||||
const auto inCall = ranges::contains(
|
const auto inCall = ranges::contains(
|
||||||
call->participants(),
|
call->participants(),
|
||||||
|
|
|
@ -230,7 +230,11 @@ public:
|
||||||
|
|
||||||
void registerGroupCall(not_null<GroupCall*> call);
|
void registerGroupCall(not_null<GroupCall*> call);
|
||||||
void unregisterGroupCall(not_null<GroupCall*> call);
|
void unregisterGroupCall(not_null<GroupCall*> call);
|
||||||
GroupCall *groupCall(CallId callId) const;
|
[[nodiscard]] GroupCall *groupCall(CallId callId) const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<GroupCall> sharedConferenceCall(
|
||||||
|
CallId id,
|
||||||
|
uint64 accessHash);
|
||||||
|
|
||||||
void watchForOffline(not_null<UserData*> user, TimeId now = 0);
|
void watchForOffline(not_null<UserData*> user, TimeId now = 0);
|
||||||
void maybeStopWatchForOffline(not_null<UserData*> user);
|
void maybeStopWatchForOffline(not_null<UserData*> user);
|
||||||
|
@ -241,7 +245,13 @@ public:
|
||||||
CallId callId,
|
CallId callId,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<UserData*> user);
|
not_null<UserData*> user);
|
||||||
void unregisterInvitedToCallUser(CallId callId, not_null<UserData*> user);
|
void registerInvitedToCallUser(
|
||||||
|
CallId callId,
|
||||||
|
GroupCall *call,
|
||||||
|
not_null<UserData*> user);
|
||||||
|
void unregisterInvitedToCallUser(
|
||||||
|
CallId callId,
|
||||||
|
not_null<UserData*> user);
|
||||||
|
|
||||||
struct InviteToCall {
|
struct InviteToCall {
|
||||||
CallId id = 0;
|
CallId id = 0;
|
||||||
|
@ -1096,10 +1106,11 @@ private:
|
||||||
|
|
||||||
base::flat_set<not_null<ViewElement*>> _heavyViewParts;
|
base::flat_set<not_null<ViewElement*>> _heavyViewParts;
|
||||||
|
|
||||||
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
base::flat_map<CallId, not_null<GroupCall*>> _groupCalls;
|
||||||
|
base::flat_map<CallId, std::weak_ptr<GroupCall>> _conferenceCalls;
|
||||||
rpl::event_stream<InviteToCall> _invitesToCalls;
|
rpl::event_stream<InviteToCall> _invitesToCalls;
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
uint64,
|
CallId,
|
||||||
base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
||||||
|
|
||||||
base::flat_set<not_null<ViewElement*>> _shownSpoilers;
|
base::flat_set<not_null<ViewElement*>> _shownSpoilers;
|
||||||
|
|
|
@ -135,13 +135,9 @@ constexpr auto kPlayStatusLimit = 2;
|
||||||
MTP_int(*creating)
|
MTP_int(*creating)
|
||||||
)).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) {
|
)).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) {
|
||||||
result.data().vcall().match([&](const auto &data) {
|
result.data().vcall().match([&](const auto &data) {
|
||||||
const auto call = std::make_shared<Data::GroupCall>(
|
const auto call = session->data().sharedConferenceCall(
|
||||||
session->user(),
|
|
||||||
data.vid().v,
|
data.vid().v,
|
||||||
data.vaccess_hash().v,
|
data.vaccess_hash().v);
|
||||||
TimeId(), // scheduleDate
|
|
||||||
false, // rtmp
|
|
||||||
true); // conference
|
|
||||||
call->processFullCall(result);
|
call->processFullCall(result);
|
||||||
const auto finished = [=](bool ok) {
|
const auto finished = [=](bool ok) {
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
|
|
@ -896,13 +896,9 @@ void SessionNavigation::resolveConferenceCall(
|
||||||
const auto inviteMsgId = base::take(_conferenceCallInviteMsgId);
|
const auto inviteMsgId = base::take(_conferenceCallInviteMsgId);
|
||||||
const auto finished = base::take(_conferenceCallResolveFinished);
|
const auto finished = base::take(_conferenceCallResolveFinished);
|
||||||
result.data().vcall().match([&](const auto &data) {
|
result.data().vcall().match([&](const auto &data) {
|
||||||
const auto call = std::make_shared<Data::GroupCall>(
|
const auto call = session().data().sharedConferenceCall(
|
||||||
session().user(),
|
|
||||||
data.vid().v,
|
data.vid().v,
|
||||||
data.vaccess_hash().v,
|
data.vaccess_hash().v);
|
||||||
TimeId(), // scheduleDate
|
|
||||||
false, // rtmp
|
|
||||||
true); // conference
|
|
||||||
call->processFullCall(result);
|
call->processFullCall(result);
|
||||||
const auto confirmed = std::make_shared<bool>();
|
const auto confirmed = std::make_shared<bool>();
|
||||||
const auto join = [=] {
|
const auto join = [=] {
|
||||||
|
|
Loading…
Add table
Reference in a new issue