mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-03 21:54:05 +02:00
PoC confcall invite, emoji.
This commit is contained in:
parent
dfe6ad3a32
commit
4401ea388c
11 changed files with 238 additions and 42 deletions
|
@ -124,9 +124,23 @@ uint64 ComputeEmojiIndex(bytes::const_span bytes) {
|
|||
} // namespace
|
||||
|
||||
std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
||||
if (!call->isKeyShaForFingerprintReady()) {
|
||||
return {};
|
||||
}
|
||||
return ComputeEmojiFingerprint(call->getKeyShaForFingerprint());
|
||||
}
|
||||
|
||||
std::vector<EmojiPtr> ComputeEmojiFingerprint(
|
||||
bytes::const_span fingerprint) {
|
||||
auto result = std::vector<EmojiPtr>();
|
||||
constexpr auto EmojiCount = (base::array_size(Offsets) - 1);
|
||||
for (auto index = 0; index != EmojiCount; ++index) {
|
||||
constexpr auto kPartSize = 8;
|
||||
for (auto partOffset = 0
|
||||
; partOffset != fingerprint.size()
|
||||
; partOffset += kPartSize) {
|
||||
auto value = ComputeEmojiIndex(
|
||||
fingerprint.subspan(partOffset, kPartSize));
|
||||
auto index = value % EmojiCount;
|
||||
auto offset = Offsets[index];
|
||||
auto size = Offsets[index + 1] - offset;
|
||||
auto string = QString::fromRawData(
|
||||
|
@ -134,22 +148,7 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
|||
size);
|
||||
auto emoji = Ui::Emoji::Find(string);
|
||||
Assert(emoji != nullptr);
|
||||
}
|
||||
if (call->isKeyShaForFingerprintReady()) {
|
||||
auto sha256 = call->getKeyShaForFingerprint();
|
||||
constexpr auto kPartSize = 8;
|
||||
for (auto partOffset = 0; partOffset != sha256.size(); partOffset += kPartSize) {
|
||||
auto value = ComputeEmojiIndex(gsl::make_span(sha256).subspan(partOffset, kPartSize));
|
||||
auto index = value % EmojiCount;
|
||||
auto offset = Offsets[index];
|
||||
auto size = Offsets[index + 1] - offset;
|
||||
auto string = QString::fromRawData(
|
||||
reinterpret_cast<const QChar*>(Data + offset),
|
||||
size);
|
||||
auto emoji = Ui::Emoji::Find(string);
|
||||
Assert(emoji != nullptr);
|
||||
result.push_back(emoji);
|
||||
}
|
||||
result.push_back(emoji);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ class Call;
|
|||
|
||||
[[nodiscard]] std::vector<EmojiPtr> ComputeEmojiFingerprint(
|
||||
not_null<Call*> call);
|
||||
[[nodiscard]] std::vector<EmojiPtr> ComputeEmojiFingerprint(
|
||||
bytes::const_span fingerprint);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
|
||||
not_null<QWidget*> parent,
|
||||
|
|
|
@ -1112,6 +1112,10 @@ bool GroupCall::rtmp() const {
|
|||
return _rtmp;
|
||||
}
|
||||
|
||||
bool GroupCall::conference() const {
|
||||
return _conferenceCall != nullptr;
|
||||
}
|
||||
|
||||
bool GroupCall::listenersHidden() const {
|
||||
return _listenersHidden;
|
||||
}
|
||||
|
@ -1152,6 +1156,12 @@ rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
|
|||
return _realChanges.events();
|
||||
}
|
||||
|
||||
rpl::producer<QByteArray> GroupCall::emojiHashValue() const {
|
||||
Expects(_e2e != nullptr);
|
||||
|
||||
return _e2e->emojiHashValue();
|
||||
}
|
||||
|
||||
void GroupCall::start(TimeId scheduleDate, bool rtmp) {
|
||||
using Flag = MTPphone_CreateGroupCall::Flag;
|
||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||
|
@ -1610,8 +1620,13 @@ void GroupCall::requestSubchainBlocks(int subchain, int height) {
|
|||
MTP_int(kShortPollChainBlocksPerRequest)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
auto &state = _subchains[subchain];
|
||||
_peer->session().api().applyUpdates(result);
|
||||
state.requestId = 0;
|
||||
state.inShortPoll = true;
|
||||
_peer->session().api().applyUpdates(result);
|
||||
state.inShortPoll = false;
|
||||
for (const auto &data : base::take(state.pending)) {
|
||||
applySubChainUpdate(subchain, data.blocks, data.next);
|
||||
}
|
||||
_e2e->subchainBlocksRequestFinished(subchain);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
auto &state = _subchains[subchain];
|
||||
|
@ -2221,11 +2236,26 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) {
|
|||
return;
|
||||
}
|
||||
auto &entry = _subchains[subchain];
|
||||
const auto inpoll = entry.requestId != 0;
|
||||
const auto &blocks = data.vblocks().v;
|
||||
const auto next = data.vnext_offset().v;
|
||||
auto now = next - int(data.vblocks().v.size());
|
||||
for (const auto &block : data.vblocks().v) {
|
||||
_e2e->apply(subchain, now++, { block.v }, inpoll);
|
||||
if (entry.requestId) {
|
||||
Assert(!entry.inShortPoll);
|
||||
entry.pending.push_back({ blocks, next });
|
||||
} else {
|
||||
applySubChainUpdate(subchain, blocks, next);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupCall::applySubChainUpdate(
|
||||
int subchain,
|
||||
const QVector<MTPbytes> &blocks,
|
||||
int next) {
|
||||
Expects(subchain >= 0 && subchain < kSubChainsCount);
|
||||
|
||||
auto &entry = _subchains[subchain];
|
||||
auto now = next - int(blocks.size());
|
||||
for (const auto &block : blocks) {
|
||||
_e2e->apply(subchain, now++, { block.v }, entry.inShortPoll);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3667,6 +3697,22 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
|
|||
}
|
||||
const auto owner = &_peer->owner();
|
||||
|
||||
if (_conferenceCall) {
|
||||
for (const auto &user : users) {
|
||||
_api.request(MTPphone_InviteConferenceCallParticipant(
|
||||
inputCall(),
|
||||
user->inputUser
|
||||
)).send();
|
||||
}
|
||||
auto result = std::variant<int, not_null<UserData*>>(0);
|
||||
if (users.size() != 1) {
|
||||
result = int(users.size());
|
||||
} else {
|
||||
result = users.front();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto count = 0;
|
||||
auto slice = QVector<MTPInputUser>();
|
||||
auto result = std::variant<int, not_null<UserData*>>(0);
|
||||
|
|
|
@ -245,6 +245,7 @@ public:
|
|||
}
|
||||
[[nodiscard]] bool scheduleStartSubscribed() const;
|
||||
[[nodiscard]] bool rtmp() const;
|
||||
[[nodiscard]] bool conference() const;
|
||||
[[nodiscard]] bool listenersHidden() const;
|
||||
[[nodiscard]] bool emptyRtmp() const;
|
||||
[[nodiscard]] rpl::producer<bool> emptyRtmpValue() const;
|
||||
|
@ -256,6 +257,7 @@ public:
|
|||
|
||||
[[nodiscard]] Data::GroupCall *lookupReal() const;
|
||||
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
|
||||
[[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const;
|
||||
|
||||
void start(TimeId scheduleDate, bool rtmp);
|
||||
void hangup();
|
||||
|
@ -479,8 +481,14 @@ private:
|
|||
ssrc = updatedSsrc;
|
||||
}
|
||||
};
|
||||
struct SubChainPending {
|
||||
QVector<MTPbytes> blocks;
|
||||
int next = 0;
|
||||
};
|
||||
struct SubChainState {
|
||||
std::vector<SubChainPending> pending;
|
||||
mtpRequestId requestId = 0;
|
||||
bool inShortPoll = false;
|
||||
};
|
||||
|
||||
friend inline constexpr bool is_flag_type(SendUpdateType) {
|
||||
|
@ -515,6 +523,10 @@ private:
|
|||
void handleUpdate(const MTPDupdateGroupCall &data);
|
||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||
void handleUpdate(const MTPDupdateGroupCallChainBlocks &data);
|
||||
void applySubChainUpdate(
|
||||
int subchain,
|
||||
const QVector<MTPbytes> &blocks,
|
||||
int next);
|
||||
bool tryCreateController();
|
||||
void destroyController();
|
||||
bool tryCreateScreencast();
|
||||
|
|
|
@ -56,6 +56,43 @@ namespace {
|
|||
return result;
|
||||
}
|
||||
|
||||
class ConfInviteController final : public ContactsBoxController {
|
||||
public:
|
||||
ConfInviteController(
|
||||
not_null<Main::Session*> session,
|
||||
base::flat_set<not_null<UserData*>> alreadyIn,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
: ContactsBoxController(session)
|
||||
, _alreadyIn(std::move(alreadyIn))
|
||||
, _choose(std::move(choose)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = ContactsBoxController::createRow(user);
|
||||
if (_alreadyIn.contains(user)) {
|
||||
result->setDisabledState(PeerListRow::State::DisabledChecked);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
_choose(row->peer());
|
||||
}
|
||||
|
||||
private:
|
||||
const base::flat_set<not_null<UserData*>> _alreadyIn;
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
InviteController::InviteController(
|
||||
|
@ -173,6 +210,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
|
|||
return nullptr;
|
||||
}
|
||||
const auto peer = call->peer();
|
||||
const auto weak = base::make_weak(call);
|
||||
auto alreadyIn = peer->owner().invitedToCallUsers(real->id());
|
||||
for (const auto &participant : real->participants()) {
|
||||
if (const auto user = participant.peer->asUser()) {
|
||||
|
@ -180,6 +218,39 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
|
|||
}
|
||||
}
|
||||
alreadyIn.emplace(peer->session().user());
|
||||
if (call->conference()) {
|
||||
const auto close = std::make_shared<Fn<void()>>();
|
||||
const auto invite = [=](not_null<PeerData*> peer) {
|
||||
Expects(peer->isUser());
|
||||
|
||||
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>(
|
||||
&real->session(),
|
||||
alreadyIn,
|
||||
invite);
|
||||
controller->setStyleOverrides(
|
||||
&st::groupCallInviteMembersList,
|
||||
&st::groupCallMultiSelect);
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setTitle(tr::lng_group_call_invite_title());
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
*close = [=] { box->closeBox(); };
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), initBox);
|
||||
}
|
||||
|
||||
auto controller = std::make_unique<InviteController>(peer, alreadyIn);
|
||||
controller->setStyleOverrides(
|
||||
&st::groupCallInviteMembersList,
|
||||
|
@ -194,7 +265,6 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
|
|||
&st::groupCallInviteMembersList,
|
||||
&st::groupCallMultiSelect);
|
||||
|
||||
const auto weak = base::make_weak(call);
|
||||
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
|
||||
const auto call = weak.get();
|
||||
if (!call) {
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "calls/group/calls_group_invite_controller.h"
|
||||
#include "calls/group/ui/calls_group_scheduled_labels.h"
|
||||
#include "calls/group/ui/desktop_capture_choose_source.h"
|
||||
#include "calls/calls_emoji_fingerprint.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/controls/call_mute_button.h"
|
||||
|
@ -76,6 +77,19 @@ constexpr auto kControlsBackgroundOpacity = 0.8;
|
|||
constexpr auto kOverrideActiveColorBgAlpha = 172;
|
||||
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] QString ComposeTitle(const QByteArray &hash) {
|
||||
auto result = tr::lng_confcall_join_title(tr::now);
|
||||
if (hash.size() >= 32) {
|
||||
const auto fp = bytes::make_span(hash).subspan(0, 32);
|
||||
const auto emoji = Calls::ComputeEmojiFingerprint(fp);
|
||||
result += QString::fromUtf8(" \xc2\xb7 ");
|
||||
for (const auto &single : emoji) {
|
||||
result += single->text();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class Show final : public Main::SessionShow {
|
||||
public:
|
||||
explicit Show(not_null<Panel*> panel);
|
||||
|
@ -367,7 +381,13 @@ void Panel::initWindow() {
|
|||
window()->setAttribute(Qt::WA_NoSystemBackground);
|
||||
window()->setTitleStyle(st::groupCallTitle);
|
||||
|
||||
subscribeToPeerChanges();
|
||||
if (_call->conference()) {
|
||||
titleText() | rpl::start_with_next([=](const QString &text) {
|
||||
window()->setTitle(text);
|
||||
}, lifetime());
|
||||
} else {
|
||||
subscribeToPeerChanges();
|
||||
}
|
||||
|
||||
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Close && handleClose()) {
|
||||
|
@ -2445,19 +2465,26 @@ void Panel::updateMembersGeometry() {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<QString> Panel::titleText() {
|
||||
if (_call->conference()) {
|
||||
return _call->emojiHashValue() | rpl::map(ComposeTitle);
|
||||
}
|
||||
return rpl::combine(
|
||||
Info::Profile::NameValue(_peer),
|
||||
rpl::single(
|
||||
QString()
|
||||
) | rpl::then(_call->real(
|
||||
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||
return real->titleValue();
|
||||
}) | rpl::flatten_latest())
|
||||
) | rpl::map([=](const QString &name, const QString &title) {
|
||||
return title.isEmpty() ? name : title;
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::refreshTitle() {
|
||||
if (!_title) {
|
||||
auto text = rpl::combine(
|
||||
Info::Profile::NameValue(_peer),
|
||||
rpl::single(
|
||||
QString()
|
||||
) | rpl::then(_call->real(
|
||||
) | rpl::map([=](not_null<Data::GroupCall*> real) {
|
||||
return real->titleValue();
|
||||
}) | rpl::flatten_latest())
|
||||
) | rpl::map([=](const QString &name, const QString &title) {
|
||||
return title.isEmpty() ? name : title;
|
||||
}) | rpl::after_next([=] {
|
||||
auto text = titleText() | rpl::after_next([=] {
|
||||
refreshTitleGeometry();
|
||||
});
|
||||
_title.create(
|
||||
|
|
|
@ -155,6 +155,7 @@ private:
|
|||
void setupMembers();
|
||||
void setupVideo(not_null<Viewport*> viewport);
|
||||
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
|
||||
[[nodiscard]] rpl::producer<QString> titleText();
|
||||
|
||||
bool handleClose();
|
||||
void startScheduledNow();
|
||||
|
|
|
@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "mainwindow.h"
|
||||
#include "main/main_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "spellcheck/spellcheck_types.h"
|
||||
#include "storage/localstorage.h"
|
||||
|
@ -1225,6 +1226,12 @@ void History::applyServiceChanges(
|
|||
topic->setHidden(mtpIsTrue(*hidden));
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageActionConferenceCall &data) {
|
||||
if (!data.is_active() && !data.is_missed()) {
|
||||
if (const auto window = session().tryResolveWindow()) {
|
||||
window->resolveConferenceCall(qs(data.vslug()), item->id);
|
||||
}
|
||||
}
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5634,6 +5634,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||
: duration
|
||||
? tr::lng_action_confcall_finished(tr::now)
|
||||
: tr::lng_action_confcall_invitation(tr::now);
|
||||
|
||||
if (duration) {
|
||||
result.text.text += " (" + QString::number(duration) + " seconds)";
|
||||
}
|
||||
|
||||
const auto id = this->id;
|
||||
const auto slug = qs(action.vslug());
|
||||
setCustomServiceLink(std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
const auto weak = my.sessionWindow;
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->resolveConferenceCall(slug, id);
|
||||
}
|
||||
}));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
@ -840,12 +840,16 @@ void SessionNavigation::resolveCollectible(
|
|||
}).send();
|
||||
}
|
||||
|
||||
void SessionNavigation::resolveConferenceCall(const QString &slug) {
|
||||
if (_conferenceCallSlug == slug) {
|
||||
void SessionNavigation::resolveConferenceCall(
|
||||
const QString &slug,
|
||||
MsgId inviteMsgId) {
|
||||
if (_conferenceCallSlug == slug
|
||||
&& _conferenceCallInviteMsgId == inviteMsgId) {
|
||||
return;
|
||||
}
|
||||
_api.request(base::take(_conferenceCallRequestId)).cancel();
|
||||
_conferenceCallSlug = slug;
|
||||
_conferenceCallInviteMsgId = inviteMsgId;
|
||||
|
||||
const auto limit = 5;
|
||||
_conferenceCallRequestId = _api.request(MTPphone_GetGroupCall(
|
||||
|
@ -864,15 +868,26 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) {
|
|||
false, // rtmp
|
||||
true); // conference
|
||||
call->processFullCall(result);
|
||||
const auto confirmed = std::make_shared<bool>();
|
||||
const auto join = [=] {
|
||||
Core::App().calls().startOrJoinConferenceCall(
|
||||
uiShow(),
|
||||
{ .call = call, .linkSlug = slug });
|
||||
*confirmed = true;
|
||||
Core::App().calls().startOrJoinConferenceCall(uiShow(), {
|
||||
.call = call,
|
||||
.linkSlug = slug,
|
||||
.joinMessageId = inviteMsgId,
|
||||
});
|
||||
};
|
||||
uiShow()->show(Box(
|
||||
const auto box = uiShow()->show(Box(
|
||||
Calls::Group::ConferenceCallJoinConfirm,
|
||||
call,
|
||||
join));
|
||||
box->boxClosing() | rpl::start_with_next([=] {
|
||||
if (inviteMsgId && !*confirmed) {
|
||||
_api.request(MTPphone_DeclineConferenceCallInvite(
|
||||
MTP_int(inviteMsgId)
|
||||
)).send();
|
||||
}
|
||||
}, box->lifetime());
|
||||
});
|
||||
}).fail([=] {
|
||||
_conferenceCallRequestId = 0;
|
||||
|
|
|
@ -264,7 +264,7 @@ public:
|
|||
PeerId ownerId,
|
||||
const QString &entity,
|
||||
Fn<void(QString)> fail = nullptr);
|
||||
void resolveConferenceCall(const QString &slug);
|
||||
void resolveConferenceCall(const QString &slug, MsgId inviteMsgId = 0);
|
||||
|
||||
base::weak_ptr<Ui::Toast::Instance> showToast(
|
||||
Ui::Toast::Config &&config);
|
||||
|
@ -331,6 +331,7 @@ private:
|
|||
mtpRequestId _collectibleRequestId = 0;
|
||||
|
||||
QString _conferenceCallSlug;
|
||||
MsgId _conferenceCallInviteMsgId;
|
||||
mtpRequestId _conferenceCallRequestId = 0;
|
||||
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue