2020-11-20 19:25:35 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
|
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
|
|
*/
|
|
|
|
#include "history/view/history_view_group_call_tracker.h"
|
|
|
|
|
|
|
|
#include "data/data_channel.h"
|
2020-11-29 12:29:25 +00:00
|
|
|
#include "data/data_user.h"
|
2020-11-20 19:25:35 +00:00
|
|
|
#include "data/data_changes.h"
|
|
|
|
#include "data/data_group_call.h"
|
|
|
|
#include "main/main_session.h"
|
|
|
|
#include "ui/chat/group_call_bar.h"
|
2020-12-25 10:10:03 +00:00
|
|
|
#include "ui/chat/group_call_userpics.h"
|
2020-11-29 12:29:25 +00:00
|
|
|
#include "ui/painter.h"
|
2021-04-22 09:13:13 +00:00
|
|
|
#include "calls/group/calls_group_call.h"
|
2020-11-24 14:40:10 +00:00
|
|
|
#include "calls/calls_instance.h"
|
|
|
|
#include "core/application.h"
|
2020-11-29 12:29:25 +00:00
|
|
|
#include "styles/style_chat.h"
|
2020-11-20 19:25:35 +00:00
|
|
|
|
|
|
|
namespace HistoryView {
|
|
|
|
|
2020-11-29 12:29:25 +00:00
|
|
|
void GenerateUserpicsInRow(
|
|
|
|
QImage &result,
|
|
|
|
const std::vector<UserpicInRow> &list,
|
2020-12-25 10:10:03 +00:00
|
|
|
const style::GroupCallUserpics &st,
|
2020-11-29 12:29:25 +00:00
|
|
|
int maxElements) {
|
|
|
|
const auto count = int(list.size());
|
|
|
|
if (!count) {
|
|
|
|
result = QImage();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto limit = std::max(count, maxElements);
|
|
|
|
const auto single = st.size;
|
|
|
|
const auto shift = st.shift;
|
|
|
|
const auto width = single + (limit - 1) * (single - shift);
|
|
|
|
if (result.width() != width * cIntRetinaFactor()) {
|
|
|
|
result = QImage(
|
|
|
|
QSize(width, single) * cIntRetinaFactor(),
|
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
|
|
}
|
|
|
|
result.fill(Qt::transparent);
|
|
|
|
result.setDevicePixelRatio(cRetinaFactor());
|
|
|
|
|
|
|
|
auto q = Painter(&result);
|
|
|
|
auto hq = PainterHighQualityEnabler(q);
|
|
|
|
auto pen = QPen(Qt::transparent);
|
|
|
|
pen.setWidth(st.stroke);
|
|
|
|
auto x = (count - 1) * (single - shift);
|
|
|
|
for (auto i = count; i != 0;) {
|
|
|
|
auto &entry = list[--i];
|
|
|
|
q.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
|
|
entry.peer->paintUserpic(q, entry.view, x, 0, single);
|
|
|
|
entry.uniqueKey = entry.peer->userpicUniqueKey(entry.view);
|
|
|
|
q.setCompositionMode(QPainter::CompositionMode_Source);
|
|
|
|
q.setBrush(Qt::NoBrush);
|
|
|
|
q.setPen(pen);
|
|
|
|
q.drawEllipse(x, 0, single, single);
|
|
|
|
x -= single - shift;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-14 12:52:18 +00:00
|
|
|
GroupCallTracker::GroupCallTracker(not_null<PeerData*> peer)
|
|
|
|
: _peer(peer) {
|
2020-11-20 19:25:35 +00:00
|
|
|
}
|
|
|
|
|
2020-11-29 12:29:25 +00:00
|
|
|
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
|
|
|
not_null<Data::GroupCall*> call,
|
2020-12-25 10:10:03 +00:00
|
|
|
int userpicSize) {
|
2020-11-29 12:29:25 +00:00
|
|
|
struct State {
|
|
|
|
std::vector<UserpicInRow> userpics;
|
|
|
|
Ui::GroupCallBarContent current;
|
|
|
|
base::has_weak_ptr guard;
|
|
|
|
bool someUserpicsNotLoaded = false;
|
|
|
|
bool scheduled = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// speaking DESC, std::max(date, lastActive) DESC
|
2021-05-07 13:01:54 +00:00
|
|
|
static const auto SortKey = [](const Data::GroupCallParticipant &p) {
|
2020-11-29 12:29:25 +00:00
|
|
|
const auto result = (p.speaking ? uint64(0x100000000ULL) : uint64(0))
|
|
|
|
| uint64(std::max(p.lastActive, p.date));
|
|
|
|
return (~uint64(0)) - result; // sorting with less(), so invert.
|
|
|
|
};
|
|
|
|
|
|
|
|
constexpr auto kLimit = 3;
|
|
|
|
static const auto FillMissingUserpics = [](
|
|
|
|
not_null<State*> state,
|
|
|
|
not_null<Data::GroupCall*> call) {
|
|
|
|
const auto already = int(state->userpics.size());
|
|
|
|
const auto &participants = call->participants();
|
|
|
|
if (already >= kLimit || participants.size() <= already) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-05-07 13:01:54 +00:00
|
|
|
std::array<const Data::GroupCallParticipant*, kLimit> adding{
|
2020-11-29 12:29:25 +00:00
|
|
|
{ nullptr }
|
|
|
|
};
|
|
|
|
for (const auto &participant : call->participants()) {
|
|
|
|
const auto alreadyInList = ranges::contains(
|
|
|
|
state->userpics,
|
2021-03-03 15:29:33 +00:00
|
|
|
participant.peer,
|
2020-11-29 12:29:25 +00:00
|
|
|
&UserpicInRow::peer);
|
|
|
|
if (alreadyInList) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (auto i = 0; i != kLimit; ++i) {
|
|
|
|
if (!adding[i]) {
|
|
|
|
adding[i] = &participant;
|
|
|
|
break;
|
|
|
|
} else if (SortKey(participant) < SortKey(*adding[i])) {
|
|
|
|
for (auto j = kLimit - 1; j != i; --j) {
|
|
|
|
adding[j] = adding[j - 1];
|
|
|
|
}
|
|
|
|
adding[i] = &participant;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto i = 0; i != kLimit - already; ++i) {
|
|
|
|
if (adding[i]) {
|
2020-12-11 11:04:34 +00:00
|
|
|
state->userpics.push_back(UserpicInRow{
|
2021-03-03 15:29:33 +00:00
|
|
|
.peer = adding[i]->peer,
|
2020-12-11 11:04:34 +00:00
|
|
|
.speaking = adding[i]->speaking,
|
|
|
|
});
|
2020-11-29 12:29:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const auto RegenerateUserpics = [](
|
|
|
|
not_null<State*> state,
|
|
|
|
not_null<Data::GroupCall*> call,
|
2020-12-25 10:10:03 +00:00
|
|
|
int userpicSize,
|
2020-11-29 12:29:25 +00:00
|
|
|
bool force = false) {
|
|
|
|
const auto result = FillMissingUserpics(state, call) || force;
|
|
|
|
if (!result) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-12-11 11:04:34 +00:00
|
|
|
state->current.users.reserve(state->userpics.size());
|
|
|
|
state->current.users.clear();
|
2020-11-29 12:29:25 +00:00
|
|
|
state->someUserpicsNotLoaded = false;
|
2020-12-11 13:16:37 +00:00
|
|
|
for (auto &userpic : state->userpics) {
|
2020-12-11 14:53:02 +00:00
|
|
|
userpic.peer->loadUserpic();
|
2020-12-25 10:10:03 +00:00
|
|
|
const auto pic = userpic.peer->genUserpic(
|
|
|
|
userpic.view,
|
|
|
|
userpicSize);
|
2020-12-11 13:16:37 +00:00
|
|
|
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
2020-12-11 11:04:34 +00:00
|
|
|
state->current.users.push_back({
|
|
|
|
.userpic = pic.toImage(),
|
|
|
|
.userpicKey = userpic.uniqueKey,
|
2021-04-01 21:04:10 +00:00
|
|
|
.id = userpic.peer->id.value,
|
2020-12-11 11:04:34 +00:00
|
|
|
.speaking = userpic.speaking,
|
|
|
|
});
|
2020-11-29 12:29:25 +00:00
|
|
|
if (userpic.peer->hasUserpic()
|
|
|
|
&& userpic.peer->useEmptyUserpic(userpic.view)) {
|
|
|
|
state->someUserpicsNotLoaded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const auto RemoveUserpic = [](
|
|
|
|
not_null<State*> state,
|
|
|
|
not_null<Data::GroupCall*> call,
|
2021-03-03 15:29:33 +00:00
|
|
|
not_null<PeerData*> participantPeer,
|
2020-12-25 10:10:03 +00:00
|
|
|
int userpicSize) {
|
2020-11-29 12:29:25 +00:00
|
|
|
const auto i = ranges::find(
|
|
|
|
state->userpics,
|
2021-03-03 15:29:33 +00:00
|
|
|
participantPeer,
|
2020-11-29 12:29:25 +00:00
|
|
|
&UserpicInRow::peer);
|
|
|
|
if (i == state->userpics.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
state->userpics.erase(i);
|
2020-12-25 10:10:03 +00:00
|
|
|
RegenerateUserpics(state, call, userpicSize, true);
|
2020-11-29 12:29:25 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const auto CheckPushToFront = [](
|
|
|
|
not_null<State*> state,
|
|
|
|
not_null<Data::GroupCall*> call,
|
2021-03-03 15:29:33 +00:00
|
|
|
not_null<PeerData*> participantPeer,
|
2020-12-25 10:10:03 +00:00
|
|
|
int userpicSize) {
|
2020-11-29 12:29:25 +00:00
|
|
|
Expects(state->userpics.size() <= kLimit);
|
|
|
|
|
|
|
|
const auto &participants = call->participants();
|
2020-12-11 11:04:34 +00:00
|
|
|
auto i = begin(state->userpics);
|
2020-11-29 12:29:25 +00:00
|
|
|
|
|
|
|
// Find where to put a new speaking userpic.
|
2020-12-11 11:04:34 +00:00
|
|
|
for (; i != end(state->userpics); ++i) {
|
2021-03-03 15:29:33 +00:00
|
|
|
if (i->peer == participantPeer) {
|
2020-12-11 11:04:34 +00:00
|
|
|
if (i->speaking) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto index = i - begin(state->userpics);
|
|
|
|
state->current.users[index].speaking = i->speaking = true;
|
|
|
|
return true;
|
2020-11-29 12:29:25 +00:00
|
|
|
}
|
|
|
|
const auto j = ranges::find(
|
|
|
|
participants,
|
2021-03-03 15:29:33 +00:00
|
|
|
i->peer,
|
2021-05-07 13:01:54 +00:00
|
|
|
&Data::GroupCallParticipant::peer);
|
2020-11-29 12:29:25 +00:00
|
|
|
if (j == end(participants) || !j->speaking) {
|
|
|
|
// Found a non-speaking one, put the new speaking one here.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i - state->userpics.begin() >= kLimit) {
|
|
|
|
// Full kLimit of speaking userpics already.
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-29 13:12:46 +00:00
|
|
|
|
|
|
|
// Add the new speaking to the place we found.
|
2020-12-11 11:04:34 +00:00
|
|
|
const auto added = state->userpics.insert(i, UserpicInRow{
|
2021-03-03 15:29:33 +00:00
|
|
|
.peer = participantPeer,
|
2020-12-11 11:04:34 +00:00
|
|
|
.speaking = true,
|
|
|
|
});
|
2020-11-29 13:12:46 +00:00
|
|
|
|
|
|
|
// Remove him from the tail, if he was there.
|
|
|
|
for (auto i = added + 1; i != state->userpics.end(); ++i) {
|
2021-03-03 15:29:33 +00:00
|
|
|
if (i->peer == participantPeer) {
|
2020-11-29 13:12:46 +00:00
|
|
|
state->userpics.erase(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-29 12:29:25 +00:00
|
|
|
if (state->userpics.size() > kLimit) {
|
|
|
|
// Find last non-speaking userpic to remove. It must be there.
|
|
|
|
for (auto i = state->userpics.end() - 1; i != added; --i) {
|
|
|
|
const auto j = ranges::find(
|
|
|
|
participants,
|
2021-03-03 15:29:33 +00:00
|
|
|
i->peer,
|
2021-05-07 13:01:54 +00:00
|
|
|
&Data::GroupCallParticipant::peer);
|
2020-11-29 12:29:25 +00:00
|
|
|
if (j == end(participants) || !j->speaking) {
|
|
|
|
// Found a non-speaking one, remove.
|
|
|
|
state->userpics.erase(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Assert(state->userpics.size() <= kLimit);
|
|
|
|
}
|
2020-12-25 10:10:03 +00:00
|
|
|
RegenerateUserpics(state, call, userpicSize, true);
|
2020-11-29 12:29:25 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
return [=](auto consumer) {
|
|
|
|
auto lifetime = rpl::lifetime();
|
|
|
|
auto state = lifetime.make_state<State>();
|
|
|
|
state->current.shown = true;
|
2021-08-31 14:59:29 +00:00
|
|
|
state->current.livestream = call->peer()->isBroadcast();
|
2020-11-29 12:29:25 +00:00
|
|
|
|
|
|
|
const auto pushNext = [=] {
|
|
|
|
if (state->scheduled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
state->scheduled = true;
|
|
|
|
crl::on_main(&state->guard, [=] {
|
|
|
|
state->scheduled = false;
|
|
|
|
consumer.put_next_copy(state->current);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
using ParticipantUpdate = Data::GroupCall::ParticipantUpdate;
|
|
|
|
call->participantUpdated(
|
|
|
|
) | rpl::start_with_next([=](const ParticipantUpdate &update) {
|
2021-03-03 15:29:33 +00:00
|
|
|
const auto participantPeer = update.now
|
|
|
|
? update.now->peer
|
|
|
|
: update.was->peer;
|
2020-11-29 12:29:25 +00:00
|
|
|
if (!update.now) {
|
2021-03-03 15:29:33 +00:00
|
|
|
const auto removed = RemoveUserpic(
|
|
|
|
state,
|
|
|
|
call,
|
|
|
|
participantPeer,
|
|
|
|
userpicSize);
|
|
|
|
if (removed) {
|
2020-11-29 12:29:25 +00:00
|
|
|
pushNext();
|
|
|
|
}
|
|
|
|
} else if (update.now->speaking
|
|
|
|
&& (!update.was || !update.was->speaking)) {
|
2021-03-03 15:29:33 +00:00
|
|
|
const auto pushed = CheckPushToFront(
|
|
|
|
state,
|
|
|
|
call,
|
|
|
|
participantPeer,
|
|
|
|
userpicSize);
|
|
|
|
if (pushed) {
|
2020-11-29 12:29:25 +00:00
|
|
|
pushNext();
|
|
|
|
}
|
2020-12-11 11:04:34 +00:00
|
|
|
} else {
|
|
|
|
auto updateSpeakingState = update.was.has_value()
|
|
|
|
&& (update.now->speaking != update.was->speaking);
|
|
|
|
if (updateSpeakingState) {
|
|
|
|
const auto i = ranges::find(
|
|
|
|
state->userpics,
|
2021-03-03 15:29:33 +00:00
|
|
|
participantPeer,
|
2020-12-11 11:04:34 +00:00
|
|
|
&UserpicInRow::peer);
|
|
|
|
if (i != end(state->userpics)) {
|
|
|
|
const auto index = i - begin(state->userpics);
|
|
|
|
state->current.users[index].speaking
|
|
|
|
= i->speaking
|
|
|
|
= update.now->speaking;
|
|
|
|
} else {
|
|
|
|
updateSpeakingState = false;
|
|
|
|
}
|
|
|
|
}
|
2020-12-25 10:10:03 +00:00
|
|
|
if (RegenerateUserpics(state, call, userpicSize)
|
2020-12-11 11:04:34 +00:00
|
|
|
|| updateSpeakingState) {
|
|
|
|
pushNext();
|
|
|
|
}
|
2020-11-29 12:29:25 +00:00
|
|
|
}
|
|
|
|
}, lifetime);
|
|
|
|
|
2021-05-03 17:43:24 +00:00
|
|
|
call->participantsReloaded(
|
2020-11-29 12:29:25 +00:00
|
|
|
) | rpl::filter([=] {
|
2020-12-25 10:10:03 +00:00
|
|
|
return RegenerateUserpics(state, call, userpicSize);
|
2020-11-29 12:29:25 +00:00
|
|
|
}) | rpl::start_with_next(pushNext, lifetime);
|
|
|
|
|
2020-12-14 12:52:18 +00:00
|
|
|
call->peer()->session().downloaderTaskFinished(
|
2020-11-29 12:29:25 +00:00
|
|
|
) | rpl::filter([=] {
|
|
|
|
return state->someUserpicsNotLoaded;
|
|
|
|
}) | rpl::start_with_next([=] {
|
|
|
|
for (const auto &userpic : state->userpics) {
|
|
|
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
|
|
|
!= userpic.uniqueKey) {
|
2020-12-25 10:10:03 +00:00
|
|
|
RegenerateUserpics(state, call, userpicSize, true);
|
2020-11-29 12:29:25 +00:00
|
|
|
pushNext();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, lifetime);
|
|
|
|
|
2020-12-25 10:10:03 +00:00
|
|
|
RegenerateUserpics(state, call, userpicSize);
|
2020-11-29 12:29:25 +00:00
|
|
|
|
2021-04-05 10:29:03 +00:00
|
|
|
rpl::combine(
|
|
|
|
call->titleValue(),
|
|
|
|
call->scheduleDateValue(),
|
|
|
|
call->fullCountValue()
|
|
|
|
) | rpl::start_with_next([=](
|
|
|
|
const QString &title,
|
|
|
|
TimeId scheduleDate,
|
|
|
|
int count) {
|
|
|
|
state->current.title = title;
|
|
|
|
state->current.scheduleDate = scheduleDate;
|
2020-11-29 12:29:25 +00:00
|
|
|
state->current.count = count;
|
2021-04-05 10:29:03 +00:00
|
|
|
state->current.shown = (count > 0) || (scheduleDate != 0);
|
2020-11-29 12:29:25 +00:00
|
|
|
consumer.put_next_copy(state->current);
|
|
|
|
}, lifetime);
|
|
|
|
|
|
|
|
return lifetime;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-20 19:25:35 +00:00
|
|
|
rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::content() const {
|
2020-12-14 12:52:18 +00:00
|
|
|
const auto peer = _peer;
|
2020-11-24 14:40:10 +00:00
|
|
|
return rpl::combine(
|
2020-12-14 12:52:18 +00:00
|
|
|
peer->session().changes().peerFlagsValue(
|
|
|
|
peer,
|
2020-11-24 14:40:10 +00:00
|
|
|
Data::PeerUpdate::Flag::GroupCall),
|
|
|
|
Core::App().calls().currentGroupCallValue()
|
2020-11-29 12:29:25 +00:00
|
|
|
) | rpl::map([=](const auto&, Calls::GroupCall *current) {
|
2020-12-14 12:52:18 +00:00
|
|
|
const auto call = peer->groupCall();
|
|
|
|
return (call && (!current || current->peer() != peer))
|
2020-11-29 12:29:25 +00:00
|
|
|
? call
|
|
|
|
: nullptr;
|
|
|
|
}) | rpl::distinct_until_changed(
|
|
|
|
) | rpl::map([](Data::GroupCall *call)
|
|
|
|
-> rpl::producer<Ui::GroupCallBarContent> {
|
|
|
|
if (!call) {
|
|
|
|
return rpl::single(Ui::GroupCallBarContent{ .shown = false });
|
2020-11-20 19:25:35 +00:00
|
|
|
} else if (!call->fullCount() && !call->participantsLoaded()) {
|
2020-11-25 14:09:59 +00:00
|
|
|
call->reload();
|
2020-11-20 19:25:35 +00:00
|
|
|
}
|
2020-12-25 10:10:03 +00:00
|
|
|
return ContentByCall(call, st::historyGroupCallUserpics.size);
|
2020-11-29 12:29:25 +00:00
|
|
|
}) | rpl::flatten_latest();
|
2020-11-20 19:25:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<> GroupCallTracker::joinClicks() const {
|
|
|
|
return _joinClicks.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace HistoryView
|