mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Allow inviting members to the group call.
This commit is contained in:
parent
9e5006dd67
commit
3a5b625d64
18 changed files with 478 additions and 48 deletions
|
@ -1062,6 +1062,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_duration_minsec_seconds#other" = "{count} sec";
|
||||
"lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}";
|
||||
|
||||
"lng_action_invite_user" = "{from} invited {user} to the voice chat";
|
||||
"lng_action_invite_users_many" = "{from} invited {users} to the voice chat";
|
||||
"lng_action_invite_users_and_one" = "{accumulated}, {user}";
|
||||
"lng_action_invite_users_and_last" = "{accumulated} and {user}";
|
||||
"lng_action_group_call" = "Group call";
|
||||
"lng_action_add_user" = "{from} added {user}";
|
||||
"lng_action_add_users_many" = "{from} added {users}";
|
||||
"lng_action_add_users_and_one" = "{accumulated}, {user}";
|
||||
|
@ -1824,12 +1829,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_group_call_leave_sure" = "Are you sure you want to leave this voice chat?";
|
||||
"lng_group_call_also_end" = "End voice chat";
|
||||
"lng_group_call_settings_title" = "Settings";
|
||||
"lng_group_call_invite_title" = "Invite members";
|
||||
"lng_group_call_invite_button" = "Invite";
|
||||
"lng_group_call_new_muted" = "Mute new members";
|
||||
"lng_group_call_speakers" = "Speakers";
|
||||
"lng_group_call_microphone" = "Microphone";
|
||||
"lng_group_call_share" = "Share Invite Link";
|
||||
"lng_group_call_end" = "End Voice Chat";
|
||||
"lng_group_call_join" = "Join";
|
||||
"lng_group_call_invite_done_user" = "You invited {user} to the voice chat.";
|
||||
"lng_group_call_invite_done_many#one" = "You invited **{count} member** to the voice chat.";
|
||||
"lng_group_call_invite_done_many#other" = "You invited **{count} members** to the voice chat.";
|
||||
|
||||
"lng_no_mic_permission" = "Telegram needs access to your microphone so that you can make calls and record voice messages.";
|
||||
|
||||
|
|
|
@ -758,6 +758,14 @@ ParticipantsBoxController::ParticipantsBoxController(
|
|||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Role role)
|
||||
: ParticipantsBoxController(CreateTag(), navigation, peer, role) {
|
||||
}
|
||||
|
||||
ParticipantsBoxController::ParticipantsBoxController(
|
||||
CreateTag,
|
||||
Window::SessionNavigation *navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Role role)
|
||||
: PeerListController(CreateSearchController(peer, role, &_additional))
|
||||
, _navigation(navigation)
|
||||
, _peer(peer)
|
||||
|
@ -795,8 +803,8 @@ void ParticipantsBoxController::setupListChangeViewers() {
|
|||
delegate()->peerListPartitionRows([&](const PeerListRow &row) {
|
||||
return (row.peer() == user);
|
||||
});
|
||||
} else {
|
||||
delegate()->peerListPrependRow(createRow(user));
|
||||
} else if (auto row = createRow(user)) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
if (_onlineSorter) {
|
||||
_onlineSorter->sort();
|
||||
|
@ -929,6 +937,8 @@ void ParticipantsBoxController::addNewItem() {
|
|||
}
|
||||
|
||||
void ParticipantsBoxController::addNewParticipants() {
|
||||
Expects(_navigation != nullptr);
|
||||
|
||||
const auto chat = _peer->asChat();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (chat) {
|
||||
|
@ -1386,6 +1396,7 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
|||
&& (_peer->isChat() || _peer->isMegagroup())) {
|
||||
showRestricted(user);
|
||||
} else {
|
||||
Assert(_navigation != nullptr);
|
||||
_navigation->showPeerInfo(user);
|
||||
}
|
||||
}
|
||||
|
@ -1413,9 +1424,11 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
|||
const auto channel = _peer->asChannel();
|
||||
const auto user = row->peer()->asUser();
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||
result->addAction(
|
||||
tr::lng_context_view_profile(tr::now),
|
||||
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
|
||||
if (_navigation) {
|
||||
result->addAction(
|
||||
tr::lng_context_view_profile(tr::now),
|
||||
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictUser(user)) {
|
||||
|
@ -1735,16 +1748,18 @@ bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
|
|||
if (delegate()->peerListFindRow(user->id)) {
|
||||
recomputeTypeFor(user);
|
||||
return false;
|
||||
} else if (auto row = createRow(user)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
delegate()->peerListAppendRow(createRow(user));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
||||
if (auto row = delegate()->peerListFindRow(user->id)) {
|
||||
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||
recomputeTypeFor(user);
|
||||
refreshCustomStatus(row);
|
||||
if (_role == Role::Admins) {
|
||||
|
@ -1752,12 +1767,14 @@ bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
|||
delegate()->peerListPrependRowFromSearchResult(row);
|
||||
}
|
||||
return false;
|
||||
} else if (auto row = createRow(user)) {
|
||||
delegate()->peerListPrependRow(std::move(row));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
delegate()->peerListPrependRow(createRow(user));
|
||||
if (_role != Role::Kicked) {
|
||||
setDescriptionText(QString());
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
|
||||
|
|
|
@ -170,6 +170,16 @@ public:
|
|||
rpl::producer<int> onlineCountValue() const override;
|
||||
|
||||
protected:
|
||||
// Allow child controllers not providing navigation.
|
||||
// This is their responsibility to override all methods that use it.
|
||||
struct CreateTag {
|
||||
};
|
||||
ParticipantsBoxController(
|
||||
CreateTag,
|
||||
Window::SessionNavigation *navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Role role);
|
||||
|
||||
virtual std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
|
||||
|
@ -236,7 +246,9 @@ private:
|
|||
void subscribeToCreatorChange(not_null<ChannelData*> channel);
|
||||
void fullListRefresh();
|
||||
|
||||
not_null<Window::SessionNavigation*> _navigation;
|
||||
// It may be nullptr in subclasses of this controller.
|
||||
Window::SessionNavigation *_navigation = nullptr;
|
||||
|
||||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
Role _role = Role::Admins;
|
||||
|
|
|
@ -425,7 +425,7 @@ groupCallMembersList: PeerList(defaultPeerList) {
|
|||
height: 52px;
|
||||
photoPosition: point(12px, 6px);
|
||||
namePosition: point(68px, 7px);
|
||||
statusPosition: point(68px, 27px);
|
||||
statusPosition: point(68px, 26px);
|
||||
photoSize: 40px;
|
||||
nameFg: groupCallMembersFg;
|
||||
nameFgChecked: groupCallMembersFg;
|
||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_session.h"
|
||||
|
||||
#include <tgcalls/group/GroupInstanceImpl.h>
|
||||
|
||||
|
@ -30,6 +31,11 @@ class GroupInstanceImpl;
|
|||
} // namespace tgcalls
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxInvitePerSlice = 10;
|
||||
|
||||
} // namespace
|
||||
|
||||
GroupCall::GroupCall(
|
||||
not_null<Delegate*> delegate,
|
||||
|
@ -505,6 +511,9 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
|
|||
}
|
||||
|
||||
void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
||||
if (!_id) {
|
||||
return;
|
||||
}
|
||||
_api.request(MTPphone_EditGroupCallMember(
|
||||
MTP_flags(mute
|
||||
? MTPphone_EditGroupCallMember::Flag::f_muted
|
||||
|
@ -523,6 +532,56 @@ void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &users) {
|
||||
const auto real = _channel->call();
|
||||
if (!real || real->id() != _id) {
|
||||
return 0;
|
||||
}
|
||||
const auto owner = &_channel->owner();
|
||||
const auto &invited = owner->invitedToCallUsers(_id);
|
||||
const auto &participants = real->participants();
|
||||
auto &&toInvite = users | ranges::view::filter([&](
|
||||
not_null<UserData*> user) {
|
||||
return !invited.contains(user) && !ranges::contains(
|
||||
participants,
|
||||
user,
|
||||
&Data::GroupCall::Participant::user);
|
||||
});
|
||||
|
||||
auto count = 0;
|
||||
auto slice = QVector<MTPInputUser>();
|
||||
auto result = std::variant<int, not_null<UserData*>>(0);
|
||||
slice.reserve(kMaxInvitePerSlice);
|
||||
const auto sendSlice = [&] {
|
||||
count += slice.size();
|
||||
_api.request(MTPphone_InviteToGroupCall(
|
||||
inputCall(),
|
||||
MTP_vector<MTPInputUser>(slice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_channel->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
slice.clear();
|
||||
};
|
||||
for (const auto user : users) {
|
||||
if (!count && slice.empty()) {
|
||||
result = user;
|
||||
}
|
||||
owner->registerInvitedToCallUser(_id, _channel, user);
|
||||
slice.push_back(user->inputUser);
|
||||
if (slice.size() == kMaxInvitePerSlice) {
|
||||
sendSlice();
|
||||
}
|
||||
}
|
||||
if (count != 0 || slice.size() != 1) {
|
||||
result = int(count + slice.size());
|
||||
}
|
||||
if (!slice.empty()) {
|
||||
sendSlice();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//void GroupCall::setAudioVolume(bool input, float level) {
|
||||
// if (_instance) {
|
||||
// if (input) {
|
||||
|
|
|
@ -96,6 +96,8 @@ public:
|
|||
void setAudioDuckingEnabled(bool enabled);
|
||||
|
||||
void toggleMute(not_null<UserData*> user, bool mute);
|
||||
std::variant<int, not_null<UserData*>> inviteUsers(
|
||||
const std::vector<not_null<UserData*>> &users);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_user.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_peer_values.h" // Data::CanWriteValue.
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
|
@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/effects/ripple_animation.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/timer.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "facades.h" // Ui::showPeerHistory.
|
||||
#include "mainwindow.h" // App::wnd()->activate.
|
||||
|
@ -613,7 +615,7 @@ auto GroupMembers::toggleMuteRequests() const
|
|||
|
||||
int GroupMembers::desiredHeight() const {
|
||||
auto desired = _header ? _header->height() : 0;
|
||||
auto count = [this] {
|
||||
auto count = [&] {
|
||||
if (const auto call = _call.get()) {
|
||||
if (const auto real = call->channel()->call()) {
|
||||
if (call->id() == real->id()) {
|
||||
|
@ -623,9 +625,10 @@ int GroupMembers::desiredHeight() const {
|
|||
}
|
||||
return 0;
|
||||
}();
|
||||
desired += std::max(count, _list->fullRowsCount())
|
||||
* st::groupCallMembersList.item.height;
|
||||
return desired;
|
||||
const auto use = std::max(count, _list->fullRowsCount());
|
||||
return (_header ? _header->height() : 0)
|
||||
+ (use * st::groupCallMembersList.item.height)
|
||||
+ (use ? st::lineWidth : 0);
|
||||
}
|
||||
|
||||
rpl::producer<int> GroupMembers::desiredHeightValue() const {
|
||||
|
@ -650,7 +653,7 @@ void GroupMembers::setupHeader(not_null<GroupCall*> call) {
|
|||
_addMember = Ui::CreateChild<Ui::IconButton>(
|
||||
parent,
|
||||
st::groupCallAddMember);
|
||||
setupButtons();
|
||||
setupButtons(call);
|
||||
|
||||
widthValue(
|
||||
) | rpl::start_with_next([this](int width) {
|
||||
|
@ -674,12 +677,14 @@ object_ptr<Ui::FlatLabel> GroupMembers::setupTitle(
|
|||
return result;
|
||||
}
|
||||
|
||||
void GroupMembers::setupButtons() {
|
||||
void GroupMembers::setupButtons(not_null<GroupCall*> call) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
_addMember->showOn(rpl::single(true));
|
||||
_addMember->showOn(Data::CanWriteValue(
|
||||
call->channel()
|
||||
));
|
||||
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
|
||||
addMember();
|
||||
_addMemberRequests.fire({});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -699,7 +704,7 @@ void GroupMembers::setupList() {
|
|||
_list->heightValue(
|
||||
) | rpl::start_with_next([=](int listHeight) {
|
||||
auto newHeight = (listHeight > 0)
|
||||
? (topSkip + listHeight)
|
||||
? (topSkip + listHeight + st::lineWidth)
|
||||
: 0;
|
||||
resize(width(), newHeight);
|
||||
}, _list->lifetime());
|
||||
|
@ -737,10 +742,6 @@ void GroupMembers::updateHeaderControlsGeometry(int newWidth) {
|
|||
_title->moveToLeft(0, 0);
|
||||
}
|
||||
|
||||
void GroupMembers::addMember() {
|
||||
// #TODO calls
|
||||
}
|
||||
|
||||
void GroupMembers::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
|
|
@ -37,6 +37,9 @@ public:
|
|||
[[nodiscard]] int desiredHeight() const;
|
||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
|
||||
[[nodiscard]] rpl::producer<> addMembersRequests() const {
|
||||
return _addMemberRequests.events();
|
||||
}
|
||||
|
||||
private:
|
||||
using ListWidget = PeerListContent;
|
||||
|
@ -66,9 +69,8 @@ private:
|
|||
object_ptr<Ui::FlatLabel> setupTitle(not_null<GroupCall*> call);
|
||||
void setupList();
|
||||
|
||||
void setupButtons();
|
||||
void setupButtons(not_null<GroupCall*> call);
|
||||
|
||||
void addMember();
|
||||
void updateHeaderControlsGeometry(int newWidth);
|
||||
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
|
@ -76,6 +78,7 @@ private:
|
|||
std::unique_ptr<PeerListController> _listController;
|
||||
object_ptr<Ui::RpWidget> _header = { nullptr };
|
||||
ListWidget *_list = { nullptr };
|
||||
rpl::event_stream<> _addMemberRequests;
|
||||
|
||||
Ui::RpWidget *_titleWrap = nullptr;
|
||||
Ui::FlatLabel *_title = nullptr;
|
||||
|
|
|
@ -16,10 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/layers/layer_manager.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
@ -33,6 +40,132 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
class InviteController final : public ParticipantsBoxController {
|
||||
public:
|
||||
InviteController(
|
||||
not_null<ChannelData*> channel,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
int fullInCount);
|
||||
|
||||
void prepare() override;
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
|
||||
void itemDeselectedHook(not_null<PeerData*> peer) override;
|
||||
|
||||
std::variant<int, not_null<UserData*>> inviteSelectedUsers(
|
||||
not_null<PeerListBox*> box,
|
||||
not_null<GroupCall*> call) const;
|
||||
|
||||
private:
|
||||
void updateTitle() const;
|
||||
[[nodiscard]] int alreadyInCount() const;
|
||||
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
|
||||
[[nodiscard]] int fullCount() const;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) const override;
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
const base::flat_set<not_null<UserData*>> _alreadyIn;
|
||||
const int _fullInCount = 0;
|
||||
mutable base::flat_set<not_null<UserData*>> _skippedUsers;
|
||||
|
||||
};
|
||||
|
||||
InviteController::InviteController(
|
||||
not_null<ChannelData*> channel,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
int fullInCount)
|
||||
: ParticipantsBoxController(CreateTag{}, nullptr, channel, Role::Members)
|
||||
, _channel(channel)
|
||||
, _alreadyIn(std::move(alreadyIn))
|
||||
, _fullInCount(std::max(fullInCount, int(_alreadyIn.size()))) {
|
||||
_skippedUsers.emplace(channel->session().user());
|
||||
}
|
||||
|
||||
void InviteController::prepare() {
|
||||
ParticipantsBoxController::prepare();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void InviteController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
int InviteController::alreadyInCount() const {
|
||||
return std::max(_fullInCount, int(_alreadyIn.size()));
|
||||
}
|
||||
|
||||
bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
|
||||
return _alreadyIn.contains(user);
|
||||
}
|
||||
|
||||
int InviteController::fullCount() const {
|
||||
return alreadyInCount() + delegate()->peerListSelectedRowsCount();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> InviteController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
if (user->isSelf() || user->isBot()) {
|
||||
if (_skippedUsers.emplace(user).second) {
|
||||
updateTitle();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<PeerListRow>(user);
|
||||
if (isAlreadyIn(user)) {
|
||||
result->setDisabledState(PeerListRow::State::DisabledChecked);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void InviteController::updateTitle() const {
|
||||
const auto inOrInvited = fullCount() - 1; // minus self
|
||||
const auto canBeInvited = std::max({
|
||||
delegate()->peerListFullRowsCount(), // minus self and bots
|
||||
_channel->membersCount() - int(_skippedUsers.size()), // self + bots
|
||||
inOrInvited
|
||||
});
|
||||
const auto additional = canBeInvited
|
||||
? qsl("%1 / %2").arg(inOrInvited).arg(canBeInvited)
|
||||
: QString();
|
||||
delegate()->peerListSetTitle(tr::lng_group_call_invite_title());
|
||||
delegate()->peerListSetAdditionalTitle(rpl::single(additional));
|
||||
}
|
||||
|
||||
std::variant<int, not_null<UserData*>> InviteController::inviteSelectedUsers(
|
||||
not_null<PeerListBox*> box,
|
||||
not_null<GroupCall*> call) const {
|
||||
const auto rows = box->peerListCollectSelectedRows();
|
||||
const auto users = ranges::view::all(
|
||||
rows
|
||||
) | ranges::view::transform([](not_null<PeerData*> peer) {
|
||||
Expects(peer->isUser());
|
||||
Expects(!peer->isSelf());
|
||||
|
||||
return not_null<UserData*>(peer->asUser());
|
||||
}) | ranges::to_vector;
|
||||
return call->inviteUsers(users);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void LeaveGroupCallBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
@ -159,13 +292,13 @@ void GroupPanel::initWidget() {
|
|||
widget()->sizeValue(
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry();
|
||||
|
||||
// title geometry depends on _controls->geometry,
|
||||
// which is not updated here yet.
|
||||
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||
}, widget()->lifetime());
|
||||
}
|
||||
|
||||
void GroupPanel::copyShareLink() {
|
||||
|
||||
}
|
||||
|
||||
void GroupPanel::hangup(bool discardCallChecked) {
|
||||
if (!_call) {
|
||||
return;
|
||||
|
@ -230,6 +363,13 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
|||
}
|
||||
}, _callLifetime);
|
||||
|
||||
_members->addMembersRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (_call) {
|
||||
addMembers();
|
||||
}
|
||||
}, _callLifetime);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
rpl::combine(
|
||||
_call->mutedValue(),
|
||||
|
@ -258,6 +398,62 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
|||
}, _callLifetime);
|
||||
}
|
||||
|
||||
void GroupPanel::addMembers() {
|
||||
const auto real = _channel->call();
|
||||
if (!_call || !real || real->id() != _call->id()) {
|
||||
return;
|
||||
}
|
||||
auto alreadyIn = _channel->owner().invitedToCallUsers(real->id());
|
||||
for (const auto &participant : real->participants()) {
|
||||
alreadyIn.emplace(participant.user);
|
||||
}
|
||||
alreadyIn.emplace(_channel->session().user());
|
||||
auto controller = std::make_unique<InviteController>(
|
||||
_channel,
|
||||
std::move(alreadyIn),
|
||||
real->fullCount());
|
||||
const auto weak = base::make_weak(_call);
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_group_call_invite_button(), [=] {
|
||||
if (const auto call = weak.get()) {
|
||||
const auto result = controller->inviteSelectedUsers(box, call);
|
||||
|
||||
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_user(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold((*user)->firstName),
|
||||
Ui::Text::WithEntities),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
} else if (const auto count = std::get_if<int>(&result)) {
|
||||
if (*count > 0) {
|
||||
Ui::Toast::Show(
|
||||
widget(),
|
||||
Ui::Toast::Config{
|
||||
.text = tr::lng_group_call_invite_done_many(
|
||||
tr::now,
|
||||
lt_count,
|
||||
*count,
|
||||
Ui::Text::RichLangValue),
|
||||
.st = &st::defaultToast,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Unexpected("Result in GroupCall::inviteUsers.");
|
||||
}
|
||||
}
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
_layerBg->showBox(Box<PeerListBox>(std::move(controller), initBox));
|
||||
}
|
||||
|
||||
void GroupPanel::initLayout() {
|
||||
initGeometry();
|
||||
|
||||
|
|
|
@ -89,9 +89,9 @@ private:
|
|||
void updateControlsGeometry();
|
||||
void showControls();
|
||||
|
||||
void copyShareLink();
|
||||
void hangup(bool discardCallChecked);
|
||||
|
||||
void addMembers();
|
||||
[[nodiscard]] int computeMembersListTop() const;
|
||||
[[nodiscard]] std::optional<QRect> computeTitleRect() const;
|
||||
void refreshTitle();
|
||||
|
|
|
@ -208,13 +208,14 @@ void GroupCall::applyParticipantsSlice(
|
|||
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
|
||||
};
|
||||
if (i == end(_participants)) {
|
||||
_userBySource.emplace(value.source, value.user);
|
||||
_userBySource.emplace(value.source, user);
|
||||
_participants.push_back(value);
|
||||
_channel->owner().unregisterInvitedToCallUser(_id, user);
|
||||
++fullCount;
|
||||
} else {
|
||||
if (i->source != value.source) {
|
||||
_userBySource.erase(i->source);
|
||||
_userBySource.emplace(value.source, value.user);
|
||||
_userBySource.emplace(value.source, user);
|
||||
}
|
||||
*i = value;
|
||||
}
|
||||
|
|
|
@ -805,6 +805,42 @@ GroupCall *Session::groupCall(uint64 callId) const {
|
|||
return (i != end(_groupCalls)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
auto Session::invitedToCallUsers(uint64 callId) const
|
||||
-> const base::flat_set<not_null<UserData*>> & {
|
||||
static const base::flat_set<not_null<UserData*>> kEmpty;
|
||||
const auto i = _invitedToCallUsers.find(callId);
|
||||
return (i != _invitedToCallUsers.end()) ? i->second : kEmpty;
|
||||
}
|
||||
|
||||
void Session::registerInvitedToCallUser(
|
||||
uint64 callId,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto call = channel->call();
|
||||
if (call && call->id() == callId) {
|
||||
const auto inCall = ranges::contains(
|
||||
call->participants(),
|
||||
user,
|
||||
&Data::GroupCall::Participant::user);
|
||||
if (inCall) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_invitedToCallUsers[callId].emplace(user);
|
||||
}
|
||||
|
||||
void Session::unregisterInvitedToCallUser(
|
||||
uint64 callId,
|
||||
not_null<UserData*> user) {
|
||||
const auto i = _invitedToCallUsers.find(callId);
|
||||
if (i != _invitedToCallUsers.end()) {
|
||||
i->second.remove(user);
|
||||
if (i->second.empty()) {
|
||||
_invitedToCallUsers.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PeerData *Session::peerByUsername(const QString &username) const {
|
||||
const auto uname = username.trimmed();
|
||||
for (const auto &[peerId, peer] : _peers) {
|
||||
|
|
|
@ -158,6 +158,14 @@ public:
|
|||
void unregisterGroupCall(not_null<GroupCall*> call);
|
||||
GroupCall *groupCall(uint64 callId) const;
|
||||
|
||||
[[nodiscard]] auto invitedToCallUsers(uint64 callId) const
|
||||
-> const base::flat_set<not_null<UserData*>> &;
|
||||
void registerInvitedToCallUser(
|
||||
uint64 callId,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
void unregisterInvitedToCallUser(uint64 callId, not_null<UserData*> user);
|
||||
|
||||
void enumerateUsers(Fn<void(not_null<UserData*>)> action) const;
|
||||
void enumerateGroups(Fn<void(not_null<PeerData*>)> action) const;
|
||||
void enumerateChannels(Fn<void(not_null<ChannelData*>)> action) const;
|
||||
|
@ -912,6 +920,7 @@ private:
|
|||
base::flat_set<not_null<ViewElement*>> _heavyViewParts;
|
||||
|
||||
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
||||
base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
||||
|
||||
History *_topPromoted = nullptr;
|
||||
|
||||
|
|
|
@ -1113,9 +1113,18 @@ ServiceAction ParseServiceAction(
|
|||
content.distance = data.vdistance().v;
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionGroupCall &data) {
|
||||
// #TODO calls
|
||||
auto content = ActionGroupCall();
|
||||
if (const auto duration = data.vduration()) {
|
||||
content.duration = duration->v;
|
||||
}
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
|
||||
// #TODO calls
|
||||
auto content = ActionInviteToGroupCall();
|
||||
content.userIds.reserve(data.vusers().v.size());
|
||||
for (const auto &user : data.vusers().v) {
|
||||
content.userIds.push_back(user.v);
|
||||
}
|
||||
result.content = content;
|
||||
}, [](const MTPDmessageActionEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -458,6 +458,14 @@ struct ActionGeoProximityReached {
|
|||
bool toSelf = false;
|
||||
};
|
||||
|
||||
struct ActionGroupCall {
|
||||
int duration = 0;
|
||||
};
|
||||
|
||||
struct ActionInviteToGroupCall {
|
||||
std::vector<int32> userIds;
|
||||
};
|
||||
|
||||
struct ServiceAction {
|
||||
std::variant<
|
||||
v::null_t,
|
||||
|
@ -482,7 +490,9 @@ struct ServiceAction {
|
|||
ActionSecureValuesSent,
|
||||
ActionContactSignUp,
|
||||
ActionPhoneNumberRequest,
|
||||
ActionGeoProximityReached> content;
|
||||
ActionGeoProximityReached,
|
||||
ActionGroupCall,
|
||||
ActionInviteToGroupCall> content;
|
||||
};
|
||||
|
||||
ServiceAction ParseServiceAction(
|
||||
|
|
|
@ -1110,10 +1110,24 @@ auto HtmlWriter::Wrap::pushMessage(
|
|||
} else if (data.toSelf) {
|
||||
return fromName + " is now within " + distance + " from you";
|
||||
} else {
|
||||
return fromName + " is now within " + distance + " from " + toName;
|
||||
return fromName
|
||||
+ " is now within "
|
||||
+ distance
|
||||
+ " from "
|
||||
+ toName;
|
||||
}
|
||||
}, [&](const ActionPhoneNumberRequest &data) {
|
||||
return serviceFrom + " requested your phone number";
|
||||
}, [&](const ActionGroupCall &data) {
|
||||
return "Group call"
|
||||
+ (data.duration
|
||||
? (" (" + QString::number(data.duration) + " seconds)")
|
||||
: QString()).toUtf8();
|
||||
}, [&](const ActionInviteToGroupCall &data) {
|
||||
return serviceFrom
|
||||
+ " invited "
|
||||
+ peers.wrapUserNames(data.userIds)
|
||||
+ " to the voice chat";
|
||||
}, [](v::null_t) { return QByteArray(); });
|
||||
|
||||
if (!serviceText.isEmpty()) {
|
||||
|
|
|
@ -487,6 +487,16 @@ QByteArray SerializeMessage(
|
|||
}, [&](const ActionPhoneNumberRequest &data) {
|
||||
pushActor();
|
||||
pushAction("requested_phone_number");
|
||||
}, [&](const ActionGroupCall &data) {
|
||||
pushActor();
|
||||
pushAction("group_call");
|
||||
if (data.duration) {
|
||||
push("duration", data.duration);
|
||||
}
|
||||
}, [&](const ActionInviteToGroupCall &data) {
|
||||
pushActor();
|
||||
pushAction("invite_to_group_call");
|
||||
pushUserNames(data.userIds);
|
||||
}, [](v::null_t) {});
|
||||
|
||||
if (v::is_null(message.action.content)) {
|
||||
|
|
|
@ -274,6 +274,49 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
|||
return result;
|
||||
};
|
||||
|
||||
auto prepareInviteToGroupCall = [this](const MTPDmessageActionInviteToGroupCall &action) {
|
||||
const auto channel = history()->peer->asChannel();
|
||||
const auto callId = action.vcall().match([&](const MTPDinputGroupCall &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
const auto owner = &history()->owner();
|
||||
const auto registerUser = [&](UserId userId) {
|
||||
const auto user = owner->user(userId);
|
||||
if (channel && callId) {
|
||||
owner->registerInvitedToCallUser(callId, channel, user);
|
||||
}
|
||||
return user;
|
||||
};
|
||||
auto result = PreparedText{};
|
||||
auto &users = action.vusers().v;
|
||||
if (users.size() == 1) {
|
||||
auto user = registerUser(users[0].v);
|
||||
result.links.push_back(fromLink());
|
||||
result.links.push_back(user->createOpenLink());
|
||||
result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name));
|
||||
} else if (users.isEmpty()) {
|
||||
result.links.push_back(fromLink());
|
||||
result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, qsl("somebody"));
|
||||
} else {
|
||||
result.links.push_back(fromLink());
|
||||
for (auto i = 0, l = users.size(); i != l; ++i) {
|
||||
auto user = registerUser(users[i].v);
|
||||
result.links.push_back(user->createOpenLink());
|
||||
|
||||
auto linkText = textcmdLink(i + 2, user->name);
|
||||
if (i == 0) {
|
||||
result.text = linkText;
|
||||
} else if (i + 1 == l) {
|
||||
result.text = tr::lng_action_invite_users_and_last(tr::now, lt_accumulated, result.text, lt_user, linkText);
|
||||
} else {
|
||||
result.text = tr::lng_action_invite_users_and_one(tr::now, lt_accumulated, result.text, lt_user, linkText);
|
||||
}
|
||||
}
|
||||
result.text = tr::lng_action_invite_users_many(tr::now, lt_from, fromLinkText(), lt_users, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto messageText = action.match([&](
|
||||
const MTPDmessageActionChatAddUser &data) {
|
||||
return prepareChatAddUserText(data);
|
||||
|
@ -324,11 +367,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
|||
LOG(("API Error: messageActionSecureValuesSentMe received."));
|
||||
return PreparedText{ tr::lng_message_empty(tr::now) };
|
||||
}, [&](const MTPDmessageActionGroupCall &data) {
|
||||
// #TODO calls
|
||||
return PreparedText{ "Group call" };
|
||||
return PreparedText{ tr::lng_action_group_call(tr::now) };
|
||||
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
|
||||
// #TODO calls
|
||||
return PreparedText{ "Invite to group call" };
|
||||
return prepareInviteToGroupCall(data);
|
||||
}, [](const MTPDmessageActionEmpty &) {
|
||||
return PreparedText{ tr::lng_message_empty(tr::now) };
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue