mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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_minsec_seconds#other" = "{count} sec";
|
||||||
"lng_duration_minutes_seconds" = "{minutes_count} {seconds_count}";
|
"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_user" = "{from} added {user}";
|
||||||
"lng_action_add_users_many" = "{from} added {users}";
|
"lng_action_add_users_many" = "{from} added {users}";
|
||||||
"lng_action_add_users_and_one" = "{accumulated}, {user}";
|
"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_leave_sure" = "Are you sure you want to leave this voice chat?";
|
||||||
"lng_group_call_also_end" = "End voice chat";
|
"lng_group_call_also_end" = "End voice chat";
|
||||||
"lng_group_call_settings_title" = "Settings";
|
"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_new_muted" = "Mute new members";
|
||||||
"lng_group_call_speakers" = "Speakers";
|
"lng_group_call_speakers" = "Speakers";
|
||||||
"lng_group_call_microphone" = "Microphone";
|
"lng_group_call_microphone" = "Microphone";
|
||||||
"lng_group_call_share" = "Share Invite Link";
|
"lng_group_call_share" = "Share Invite Link";
|
||||||
"lng_group_call_end" = "End Voice Chat";
|
"lng_group_call_end" = "End Voice Chat";
|
||||||
"lng_group_call_join" = "Join";
|
"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.";
|
"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<Window::SessionNavigation*> navigation,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
Role role)
|
Role role)
|
||||||
|
: ParticipantsBoxController(CreateTag(), navigation, peer, role) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticipantsBoxController::ParticipantsBoxController(
|
||||||
|
CreateTag,
|
||||||
|
Window::SessionNavigation *navigation,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
Role role)
|
||||||
: PeerListController(CreateSearchController(peer, role, &_additional))
|
: PeerListController(CreateSearchController(peer, role, &_additional))
|
||||||
, _navigation(navigation)
|
, _navigation(navigation)
|
||||||
, _peer(peer)
|
, _peer(peer)
|
||||||
|
@ -795,8 +803,8 @@ void ParticipantsBoxController::setupListChangeViewers() {
|
||||||
delegate()->peerListPartitionRows([&](const PeerListRow &row) {
|
delegate()->peerListPartitionRows([&](const PeerListRow &row) {
|
||||||
return (row.peer() == user);
|
return (row.peer() == user);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (auto row = createRow(user)) {
|
||||||
delegate()->peerListPrependRow(createRow(user));
|
delegate()->peerListPrependRow(std::move(row));
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
if (_onlineSorter) {
|
if (_onlineSorter) {
|
||||||
_onlineSorter->sort();
|
_onlineSorter->sort();
|
||||||
|
@ -929,6 +937,8 @@ void ParticipantsBoxController::addNewItem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticipantsBoxController::addNewParticipants() {
|
void ParticipantsBoxController::addNewParticipants() {
|
||||||
|
Expects(_navigation != nullptr);
|
||||||
|
|
||||||
const auto chat = _peer->asChat();
|
const auto chat = _peer->asChat();
|
||||||
const auto channel = _peer->asChannel();
|
const auto channel = _peer->asChannel();
|
||||||
if (chat) {
|
if (chat) {
|
||||||
|
@ -1386,6 +1396,7 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
&& (_peer->isChat() || _peer->isMegagroup())) {
|
&& (_peer->isChat() || _peer->isMegagroup())) {
|
||||||
showRestricted(user);
|
showRestricted(user);
|
||||||
} else {
|
} else {
|
||||||
|
Assert(_navigation != nullptr);
|
||||||
_navigation->showPeerInfo(user);
|
_navigation->showPeerInfo(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1413,9 +1424,11 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||||
const auto channel = _peer->asChannel();
|
const auto channel = _peer->asChannel();
|
||||||
const auto user = row->peer()->asUser();
|
const auto user = row->peer()->asUser();
|
||||||
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
auto result = base::make_unique_q<Ui::PopupMenu>(parent);
|
||||||
result->addAction(
|
if (_navigation) {
|
||||||
tr::lng_context_view_profile(tr::now),
|
result->addAction(
|
||||||
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
|
tr::lng_context_view_profile(tr::now),
|
||||||
|
crl::guard(this, [=] { _navigation->showPeerInfo(user); }));
|
||||||
|
}
|
||||||
if (_role == Role::Kicked) {
|
if (_role == Role::Kicked) {
|
||||||
if (_peer->isMegagroup()
|
if (_peer->isMegagroup()
|
||||||
&& _additional.canRestrictUser(user)) {
|
&& _additional.canRestrictUser(user)) {
|
||||||
|
@ -1735,16 +1748,18 @@ bool ParticipantsBoxController::appendRow(not_null<UserData*> user) {
|
||||||
if (delegate()->peerListFindRow(user->id)) {
|
if (delegate()->peerListFindRow(user->id)) {
|
||||||
recomputeTypeFor(user);
|
recomputeTypeFor(user);
|
||||||
return false;
|
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));
|
return false;
|
||||||
if (_role != Role::Kicked) {
|
|
||||||
setDescriptionText(QString());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
||||||
if (auto row = delegate()->peerListFindRow(user->id)) {
|
if (const auto row = delegate()->peerListFindRow(user->id)) {
|
||||||
recomputeTypeFor(user);
|
recomputeTypeFor(user);
|
||||||
refreshCustomStatus(row);
|
refreshCustomStatus(row);
|
||||||
if (_role == Role::Admins) {
|
if (_role == Role::Admins) {
|
||||||
|
@ -1752,12 +1767,14 @@ bool ParticipantsBoxController::prependRow(not_null<UserData*> user) {
|
||||||
delegate()->peerListPrependRowFromSearchResult(row);
|
delegate()->peerListPrependRowFromSearchResult(row);
|
||||||
}
|
}
|
||||||
return false;
|
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));
|
return false;
|
||||||
if (_role != Role::Kicked) {
|
|
||||||
setDescriptionText(QString());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
|
bool ParticipantsBoxController::removeRow(not_null<UserData*> user) {
|
||||||
|
|
|
@ -170,6 +170,16 @@ public:
|
||||||
rpl::producer<int> onlineCountValue() const override;
|
rpl::producer<int> onlineCountValue() const override;
|
||||||
|
|
||||||
protected:
|
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(
|
virtual std::unique_ptr<PeerListRow> createRow(
|
||||||
not_null<UserData*> user) const;
|
not_null<UserData*> user) const;
|
||||||
|
|
||||||
|
@ -236,7 +246,9 @@ private:
|
||||||
void subscribeToCreatorChange(not_null<ChannelData*> channel);
|
void subscribeToCreatorChange(not_null<ChannelData*> channel);
|
||||||
void fullListRefresh();
|
void fullListRefresh();
|
||||||
|
|
||||||
not_null<Window::SessionNavigation*> _navigation;
|
// It may be nullptr in subclasses of this controller.
|
||||||
|
Window::SessionNavigation *_navigation = nullptr;
|
||||||
|
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
Role _role = Role::Admins;
|
Role _role = Role::Admins;
|
||||||
|
|
|
@ -425,7 +425,7 @@ groupCallMembersList: PeerList(defaultPeerList) {
|
||||||
height: 52px;
|
height: 52px;
|
||||||
photoPosition: point(12px, 6px);
|
photoPosition: point(12px, 6px);
|
||||||
namePosition: point(68px, 7px);
|
namePosition: point(68px, 7px);
|
||||||
statusPosition: point(68px, 27px);
|
statusPosition: point(68px, 26px);
|
||||||
photoSize: 40px;
|
photoSize: 40px;
|
||||||
nameFg: groupCallMembersFg;
|
nameFg: groupCallMembersFg;
|
||||||
nameFgChecked: groupCallMembersFg;
|
nameFgChecked: groupCallMembersFg;
|
||||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
|
||||||
#include <tgcalls/group/GroupInstanceImpl.h>
|
#include <tgcalls/group/GroupInstanceImpl.h>
|
||||||
|
|
||||||
|
@ -30,6 +31,11 @@ class GroupInstanceImpl;
|
||||||
} // namespace tgcalls
|
} // namespace tgcalls
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxInvitePerSlice = 10;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
GroupCall::GroupCall(
|
GroupCall::GroupCall(
|
||||||
not_null<Delegate*> delegate,
|
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) {
|
void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
||||||
|
if (!_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_api.request(MTPphone_EditGroupCallMember(
|
_api.request(MTPphone_EditGroupCallMember(
|
||||||
MTP_flags(mute
|
MTP_flags(mute
|
||||||
? MTPphone_EditGroupCallMember::Flag::f_muted
|
? MTPphone_EditGroupCallMember::Flag::f_muted
|
||||||
|
@ -523,6 +532,56 @@ void GroupCall::toggleMute(not_null<UserData*> user, bool mute) {
|
||||||
}).send();
|
}).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) {
|
//void GroupCall::setAudioVolume(bool input, float level) {
|
||||||
// if (_instance) {
|
// if (_instance) {
|
||||||
// if (input) {
|
// if (input) {
|
||||||
|
|
|
@ -96,6 +96,8 @@ public:
|
||||||
void setAudioDuckingEnabled(bool enabled);
|
void setAudioDuckingEnabled(bool enabled);
|
||||||
|
|
||||||
void toggleMute(not_null<UserData*> user, bool mute);
|
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() {
|
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||||
return _lifetime;
|
return _lifetime;
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_group_call.h"
|
#include "data/data_group_call.h"
|
||||||
|
#include "data/data_peer_values.h" // Data::CanWriteValue.
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "ui/widgets/popup_menu.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 "ui/effects/ripple_animation.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
|
#include "boxes/peers/edit_participants_box.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "facades.h" // Ui::showPeerHistory.
|
#include "facades.h" // Ui::showPeerHistory.
|
||||||
#include "mainwindow.h" // App::wnd()->activate.
|
#include "mainwindow.h" // App::wnd()->activate.
|
||||||
|
@ -613,7 +615,7 @@ auto GroupMembers::toggleMuteRequests() const
|
||||||
|
|
||||||
int GroupMembers::desiredHeight() const {
|
int GroupMembers::desiredHeight() const {
|
||||||
auto desired = _header ? _header->height() : 0;
|
auto desired = _header ? _header->height() : 0;
|
||||||
auto count = [this] {
|
auto count = [&] {
|
||||||
if (const auto call = _call.get()) {
|
if (const auto call = _call.get()) {
|
||||||
if (const auto real = call->channel()->call()) {
|
if (const auto real = call->channel()->call()) {
|
||||||
if (call->id() == real->id()) {
|
if (call->id() == real->id()) {
|
||||||
|
@ -623,9 +625,10 @@ int GroupMembers::desiredHeight() const {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}();
|
}();
|
||||||
desired += std::max(count, _list->fullRowsCount())
|
const auto use = std::max(count, _list->fullRowsCount());
|
||||||
* st::groupCallMembersList.item.height;
|
return (_header ? _header->height() : 0)
|
||||||
return desired;
|
+ (use * st::groupCallMembersList.item.height)
|
||||||
|
+ (use ? st::lineWidth : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<int> GroupMembers::desiredHeightValue() const {
|
rpl::producer<int> GroupMembers::desiredHeightValue() const {
|
||||||
|
@ -650,7 +653,7 @@ void GroupMembers::setupHeader(not_null<GroupCall*> call) {
|
||||||
_addMember = Ui::CreateChild<Ui::IconButton>(
|
_addMember = Ui::CreateChild<Ui::IconButton>(
|
||||||
parent,
|
parent,
|
||||||
st::groupCallAddMember);
|
st::groupCallAddMember);
|
||||||
setupButtons();
|
setupButtons(call);
|
||||||
|
|
||||||
widthValue(
|
widthValue(
|
||||||
) | rpl::start_with_next([this](int width) {
|
) | rpl::start_with_next([this](int width) {
|
||||||
|
@ -674,12 +677,14 @@ object_ptr<Ui::FlatLabel> GroupMembers::setupTitle(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupMembers::setupButtons() {
|
void GroupMembers::setupButtons(not_null<GroupCall*> call) {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
_addMember->showOn(rpl::single(true));
|
_addMember->showOn(Data::CanWriteValue(
|
||||||
|
call->channel()
|
||||||
|
));
|
||||||
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
|
_addMember->addClickHandler([=] { // TODO throttle(ripple duration)
|
||||||
addMember();
|
_addMemberRequests.fire({});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,7 +704,7 @@ void GroupMembers::setupList() {
|
||||||
_list->heightValue(
|
_list->heightValue(
|
||||||
) | rpl::start_with_next([=](int listHeight) {
|
) | rpl::start_with_next([=](int listHeight) {
|
||||||
auto newHeight = (listHeight > 0)
|
auto newHeight = (listHeight > 0)
|
||||||
? (topSkip + listHeight)
|
? (topSkip + listHeight + st::lineWidth)
|
||||||
: 0;
|
: 0;
|
||||||
resize(width(), newHeight);
|
resize(width(), newHeight);
|
||||||
}, _list->lifetime());
|
}, _list->lifetime());
|
||||||
|
@ -737,10 +742,6 @@ void GroupMembers::updateHeaderControlsGeometry(int newWidth) {
|
||||||
_title->moveToLeft(0, 0);
|
_title->moveToLeft(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupMembers::addMember() {
|
|
||||||
// #TODO calls
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupMembers::visibleTopBottomUpdated(
|
void GroupMembers::visibleTopBottomUpdated(
|
||||||
int visibleTop,
|
int visibleTop,
|
||||||
int visibleBottom) {
|
int visibleBottom) {
|
||||||
|
|
|
@ -37,6 +37,9 @@ public:
|
||||||
[[nodiscard]] int desiredHeight() const;
|
[[nodiscard]] int desiredHeight() const;
|
||||||
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
[[nodiscard]] rpl::producer<int> desiredHeightValue() const override;
|
||||||
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
|
[[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<> addMembersRequests() const {
|
||||||
|
return _addMemberRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using ListWidget = PeerListContent;
|
using ListWidget = PeerListContent;
|
||||||
|
@ -66,9 +69,8 @@ private:
|
||||||
object_ptr<Ui::FlatLabel> setupTitle(not_null<GroupCall*> call);
|
object_ptr<Ui::FlatLabel> setupTitle(not_null<GroupCall*> call);
|
||||||
void setupList();
|
void setupList();
|
||||||
|
|
||||||
void setupButtons();
|
void setupButtons(not_null<GroupCall*> call);
|
||||||
|
|
||||||
void addMember();
|
|
||||||
void updateHeaderControlsGeometry(int newWidth);
|
void updateHeaderControlsGeometry(int newWidth);
|
||||||
|
|
||||||
const base::weak_ptr<GroupCall> _call;
|
const base::weak_ptr<GroupCall> _call;
|
||||||
|
@ -76,6 +78,7 @@ private:
|
||||||
std::unique_ptr<PeerListController> _listController;
|
std::unique_ptr<PeerListController> _listController;
|
||||||
object_ptr<Ui::RpWidget> _header = { nullptr };
|
object_ptr<Ui::RpWidget> _header = { nullptr };
|
||||||
ListWidget *_list = { nullptr };
|
ListWidget *_list = { nullptr };
|
||||||
|
rpl::event_stream<> _addMemberRequests;
|
||||||
|
|
||||||
Ui::RpWidget *_titleWrap = nullptr;
|
Ui::RpWidget *_titleWrap = nullptr;
|
||||||
Ui::FlatLabel *_title = nullptr;
|
Ui::FlatLabel *_title = nullptr;
|
||||||
|
|
|
@ -16,10 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/layers/layer_manager.h"
|
#include "ui/layers/layer_manager.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "data/data_channel.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 "base/event_filter.h"
|
||||||
|
#include "boxes/peers/edit_participants_box.h"
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
@ -33,6 +40,132 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QWindow>
|
#include <QtGui/QWindow>
|
||||||
|
|
||||||
namespace Calls {
|
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(
|
void LeaveGroupCallBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
|
@ -159,13 +292,13 @@ void GroupPanel::initWidget() {
|
||||||
widget()->sizeValue(
|
widget()->sizeValue(
|
||||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||||
updateControlsGeometry();
|
updateControlsGeometry();
|
||||||
|
|
||||||
|
// title geometry depends on _controls->geometry,
|
||||||
|
// which is not updated here yet.
|
||||||
|
crl::on_main(widget(), [=] { refreshTitle(); });
|
||||||
}, widget()->lifetime());
|
}, widget()->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupPanel::copyShareLink() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupPanel::hangup(bool discardCallChecked) {
|
void GroupPanel::hangup(bool discardCallChecked) {
|
||||||
if (!_call) {
|
if (!_call) {
|
||||||
return;
|
return;
|
||||||
|
@ -230,6 +363,13 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||||
}
|
}
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
|
_members->addMembersRequests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
if (_call) {
|
||||||
|
addMembers();
|
||||||
|
}
|
||||||
|
}, _callLifetime);
|
||||||
|
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
_call->mutedValue(),
|
_call->mutedValue(),
|
||||||
|
@ -258,6 +398,62 @@ void GroupPanel::initWithCall(GroupCall *call) {
|
||||||
}, _callLifetime);
|
}, _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() {
|
void GroupPanel::initLayout() {
|
||||||
initGeometry();
|
initGeometry();
|
||||||
|
|
||||||
|
|
|
@ -89,9 +89,9 @@ private:
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
void showControls();
|
void showControls();
|
||||||
|
|
||||||
void copyShareLink();
|
|
||||||
void hangup(bool discardCallChecked);
|
void hangup(bool discardCallChecked);
|
||||||
|
|
||||||
|
void addMembers();
|
||||||
[[nodiscard]] int computeMembersListTop() const;
|
[[nodiscard]] int computeMembersListTop() const;
|
||||||
[[nodiscard]] std::optional<QRect> computeTitleRect() const;
|
[[nodiscard]] std::optional<QRect> computeTitleRect() const;
|
||||||
void refreshTitle();
|
void refreshTitle();
|
||||||
|
|
|
@ -208,13 +208,14 @@ void GroupCall::applyParticipantsSlice(
|
||||||
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
|
.canSelfUnmute = !data.is_muted() || data.is_can_self_unmute(),
|
||||||
};
|
};
|
||||||
if (i == end(_participants)) {
|
if (i == end(_participants)) {
|
||||||
_userBySource.emplace(value.source, value.user);
|
_userBySource.emplace(value.source, user);
|
||||||
_participants.push_back(value);
|
_participants.push_back(value);
|
||||||
|
_channel->owner().unregisterInvitedToCallUser(_id, user);
|
||||||
++fullCount;
|
++fullCount;
|
||||||
} else {
|
} else {
|
||||||
if (i->source != value.source) {
|
if (i->source != value.source) {
|
||||||
_userBySource.erase(i->source);
|
_userBySource.erase(i->source);
|
||||||
_userBySource.emplace(value.source, value.user);
|
_userBySource.emplace(value.source, user);
|
||||||
}
|
}
|
||||||
*i = value;
|
*i = value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -805,6 +805,42 @@ GroupCall *Session::groupCall(uint64 callId) const {
|
||||||
return (i != end(_groupCalls)) ? i->second.get() : nullptr;
|
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 {
|
PeerData *Session::peerByUsername(const QString &username) const {
|
||||||
const auto uname = username.trimmed();
|
const auto uname = username.trimmed();
|
||||||
for (const auto &[peerId, peer] : _peers) {
|
for (const auto &[peerId, peer] : _peers) {
|
||||||
|
|
|
@ -158,6 +158,14 @@ public:
|
||||||
void unregisterGroupCall(not_null<GroupCall*> call);
|
void unregisterGroupCall(not_null<GroupCall*> call);
|
||||||
GroupCall *groupCall(uint64 callId) const;
|
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 enumerateUsers(Fn<void(not_null<UserData*>)> action) const;
|
||||||
void enumerateGroups(Fn<void(not_null<PeerData*>)> action) const;
|
void enumerateGroups(Fn<void(not_null<PeerData*>)> action) const;
|
||||||
void enumerateChannels(Fn<void(not_null<ChannelData*>)> 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_set<not_null<ViewElement*>> _heavyViewParts;
|
||||||
|
|
||||||
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
||||||
|
base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
||||||
|
|
||||||
History *_topPromoted = nullptr;
|
History *_topPromoted = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -1113,9 +1113,18 @@ ServiceAction ParseServiceAction(
|
||||||
content.distance = data.vdistance().v;
|
content.distance = data.vdistance().v;
|
||||||
result.content = content;
|
result.content = content;
|
||||||
}, [&](const MTPDmessageActionGroupCall &data) {
|
}, [&](const MTPDmessageActionGroupCall &data) {
|
||||||
// #TODO calls
|
auto content = ActionGroupCall();
|
||||||
|
if (const auto duration = data.vduration()) {
|
||||||
|
content.duration = duration->v;
|
||||||
|
}
|
||||||
|
result.content = content;
|
||||||
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
|
}, [&](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) {});
|
}, [](const MTPDmessageActionEmpty &data) {});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,6 +458,14 @@ struct ActionGeoProximityReached {
|
||||||
bool toSelf = false;
|
bool toSelf = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ActionGroupCall {
|
||||||
|
int duration = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ActionInviteToGroupCall {
|
||||||
|
std::vector<int32> userIds;
|
||||||
|
};
|
||||||
|
|
||||||
struct ServiceAction {
|
struct ServiceAction {
|
||||||
std::variant<
|
std::variant<
|
||||||
v::null_t,
|
v::null_t,
|
||||||
|
@ -482,7 +490,9 @@ struct ServiceAction {
|
||||||
ActionSecureValuesSent,
|
ActionSecureValuesSent,
|
||||||
ActionContactSignUp,
|
ActionContactSignUp,
|
||||||
ActionPhoneNumberRequest,
|
ActionPhoneNumberRequest,
|
||||||
ActionGeoProximityReached> content;
|
ActionGeoProximityReached,
|
||||||
|
ActionGroupCall,
|
||||||
|
ActionInviteToGroupCall> content;
|
||||||
};
|
};
|
||||||
|
|
||||||
ServiceAction ParseServiceAction(
|
ServiceAction ParseServiceAction(
|
||||||
|
|
|
@ -1110,10 +1110,24 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||||
} else if (data.toSelf) {
|
} else if (data.toSelf) {
|
||||||
return fromName + " is now within " + distance + " from you";
|
return fromName + " is now within " + distance + " from you";
|
||||||
} else {
|
} else {
|
||||||
return fromName + " is now within " + distance + " from " + toName;
|
return fromName
|
||||||
|
+ " is now within "
|
||||||
|
+ distance
|
||||||
|
+ " from "
|
||||||
|
+ toName;
|
||||||
}
|
}
|
||||||
}, [&](const ActionPhoneNumberRequest &data) {
|
}, [&](const ActionPhoneNumberRequest &data) {
|
||||||
return serviceFrom + " requested your phone number";
|
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(); });
|
}, [](v::null_t) { return QByteArray(); });
|
||||||
|
|
||||||
if (!serviceText.isEmpty()) {
|
if (!serviceText.isEmpty()) {
|
||||||
|
|
|
@ -487,6 +487,16 @@ QByteArray SerializeMessage(
|
||||||
}, [&](const ActionPhoneNumberRequest &data) {
|
}, [&](const ActionPhoneNumberRequest &data) {
|
||||||
pushActor();
|
pushActor();
|
||||||
pushAction("requested_phone_number");
|
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) {});
|
}, [](v::null_t) {});
|
||||||
|
|
||||||
if (v::is_null(message.action.content)) {
|
if (v::is_null(message.action.content)) {
|
||||||
|
|
|
@ -274,6 +274,49 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
||||||
return result;
|
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 auto messageText = action.match([&](
|
||||||
const MTPDmessageActionChatAddUser &data) {
|
const MTPDmessageActionChatAddUser &data) {
|
||||||
return prepareChatAddUserText(data);
|
return prepareChatAddUserText(data);
|
||||||
|
@ -324,11 +367,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
||||||
LOG(("API Error: messageActionSecureValuesSentMe received."));
|
LOG(("API Error: messageActionSecureValuesSentMe received."));
|
||||||
return PreparedText{ tr::lng_message_empty(tr::now) };
|
return PreparedText{ tr::lng_message_empty(tr::now) };
|
||||||
}, [&](const MTPDmessageActionGroupCall &data) {
|
}, [&](const MTPDmessageActionGroupCall &data) {
|
||||||
// #TODO calls
|
return PreparedText{ tr::lng_action_group_call(tr::now) };
|
||||||
return PreparedText{ "Group call" };
|
|
||||||
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
|
}, [&](const MTPDmessageActionInviteToGroupCall &data) {
|
||||||
// #TODO calls
|
return prepareInviteToGroupCall(data);
|
||||||
return PreparedText{ "Invite to group call" };
|
|
||||||
}, [](const MTPDmessageActionEmpty &) {
|
}, [](const MTPDmessageActionEmpty &) {
|
||||||
return PreparedText{ tr::lng_message_empty(tr::now) };
|
return PreparedText{ tr::lng_message_empty(tr::now) };
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue