Add raised hand display in participants list.

This commit is contained in:
John Preston 2021-03-09 11:24:29 +04:00
parent fb579f1c10
commit 361e3565d4
11 changed files with 63 additions and 35 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1923,6 +1923,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_title" = "Voice Chat"; "lng_group_call_title" = "Voice Chat";
"lng_group_call_active" = "speaking"; "lng_group_call_active" = "speaking";
"lng_group_call_inactive" = "listening"; "lng_group_call_inactive" = "listening";
"lng_group_call_raised_hand_status" = "wants to speak";
"lng_group_call_settings" = "Settings"; "lng_group_call_settings" = "Settings";
"lng_group_call_unmute" = "Unmute"; "lng_group_call_unmute" = "Unmute";
"lng_group_call_unmute_sub" = "or hold spacebar to talk"; "lng_group_call_unmute_sub" = "or hold spacebar to talk";

View file

@ -588,6 +588,7 @@ groupCallMemberColoredCrossLine: CrossLineAnimation(groupCallMemberInactiveCross
} }
groupCallMemberInvited: icon {{ "calls/group_calls_invited", groupCallMemberInactiveIcon }}; groupCallMemberInvited: icon {{ "calls/group_calls_invited", groupCallMemberInactiveIcon }};
groupCallMemberInvitedPosition: point(2px, 12px); groupCallMemberInvitedPosition: point(2px, 12px);
groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMemberInactiveStatus }};
groupCallSettings: CallButton(callMicrophoneMute) { groupCallSettings: CallButton(callMicrophoneMute) {
button: IconButton(callButton) { button: IconButton(callButton) {

View file

@ -79,16 +79,20 @@ class Row;
class RowDelegate { class RowDelegate {
public: public:
struct IconState {
float64 speaking = 0.;
float64 active = 0.;
float64 muted = 0.;
bool mutedByMe = false;
bool raisedHand = false;
};
virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0; virtual bool rowIsMe(not_null<PeerData*> participantPeer) = 0;
virtual bool rowCanMuteMembers() = 0; virtual bool rowCanMuteMembers() = 0;
virtual void rowUpdateRow(not_null<Row*> row) = 0; virtual void rowUpdateRow(not_null<Row*> row) = 0;
virtual void rowPaintIcon( virtual void rowPaintIcon(
Painter &p, Painter &p,
QRect rect, QRect rect,
float64 speaking, IconState state) = 0;
float64 active,
float64 muted,
bool mutedByMe) = 0;
}; };
class Row final : public PeerListRow { class Row final : public PeerListRow {
@ -101,6 +105,7 @@ public:
Active, Active,
Inactive, Inactive,
Muted, Muted,
RaisedHand,
MutedByMe, MutedByMe,
Invited, Invited,
}; };
@ -281,10 +286,7 @@ public:
void rowPaintIcon( void rowPaintIcon(
Painter &p, Painter &p,
QRect rect, QRect rect,
float64 speaking, IconState state) override;
float64 active,
float64 muted,
bool mutedByMe) override;
private: private:
[[nodiscard]] std::unique_ptr<Row> createRowForMe(); [[nodiscard]] std::unique_ptr<Row> createRowForMe();
@ -383,14 +385,19 @@ void Row::updateState(const Data::GroupCall::Participant *participant) {
setSounding(participant->sounding && participant->ssrc != 0); setSounding(participant->sounding && participant->ssrc != 0);
setSpeaking(participant->speaking && participant->ssrc != 0); setSpeaking(participant->speaking && participant->ssrc != 0);
} else if (participant->canSelfUnmute) { } else if (participant->canSelfUnmute) {
setState(participant->mutedByMe ? State::MutedByMe : State::Inactive); setState(participant->mutedByMe
? State::MutedByMe
: State::Inactive);
setSounding(false); setSounding(false);
setSpeaking(false); setSpeaking(false);
} else { } else {
setState(State::Muted); setState(participant->raisedHandRating
? State::RaisedHand
: State::Muted);
setSounding(false); setSounding(false);
setSpeaking(false); setSpeaking(false);
} }
refreshStatus();
} }
void Row::setSpeaking(bool speaking) { void Row::setSpeaking(bool speaking) {
@ -406,7 +413,8 @@ void Row::setSpeaking(bool speaking) {
if (!_speaking if (!_speaking
|| (_state == State::MutedByMe) || (_state == State::MutedByMe)
|| (_state == State::Muted)) { || (_state == State::Muted)
|| (_state == State::RaisedHand)) {
_statusIcon = nullptr; _statusIcon = nullptr;
} else if (!_statusIcon) { } else if (!_statusIcon) {
_statusIcon = std::make_unique<StatusIcon>( _statusIcon = std::make_unique<StatusIcon>(
@ -456,7 +464,6 @@ void Row::setSounding(bool sounding) {
_blobsAnimation->lastTime = crl::now(); _blobsAnimation->lastTime = crl::now();
updateLevel(GroupCall::kSpeakLevelThreshold); updateLevel(GroupCall::kSpeakLevelThreshold);
} }
refreshStatus();
} }
void Row::setState(State state) { void Row::setState(State state) {
@ -464,10 +471,12 @@ void Row::setState(State state) {
return; return;
} }
const auto wasActive = (_state == State::Active); const auto wasActive = (_state == State::Active);
const auto wasMuted = (_state == State::Muted); const auto wasMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
_state = state; _state = state;
const auto nowActive = (_state == State::Active); const auto nowActive = (_state == State::Active);
const auto nowMuted = (_state == State::Muted); const auto nowMuted = (_state == State::Muted)
|| (_state == State::RaisedHand);
if (nowActive != wasActive) { if (nowActive != wasActive) {
_activeAnimation.start( _activeAnimation.start(
[=] { _delegate->rowUpdateRow(this); }, [=] { _delegate->rowUpdateRow(this); },
@ -679,7 +688,9 @@ void Row::paintStatusText(
int outerWidth, int outerWidth,
bool selected) { bool selected) {
const auto &font = st::normalFont; const auto &font = st::normalFont;
const auto about = (_state == State::Inactive || _state == State::Muted) const auto about = (_state == State::Inactive
|| _state == State::Muted
|| _state == State::RaisedHand)
? _aboutText ? _aboutText
: QString(); : QString();
if (_aboutText.isEmpty() if (_aboutText.isEmpty()
@ -752,12 +763,17 @@ void Row::paintAction(
} }
} }
const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.); const auto speaking = _speakingAnimation.value(_speaking ? 1. : 0.);
const auto active = _activeAnimation.value( const auto active = _activeAnimation.value((_state == State::Active) ? 1. : 0.);
(_state == State::Active) ? 1. : 0.);
const auto muted = _mutedAnimation.value( const auto muted = _mutedAnimation.value(
(_state == State::Muted) ? 1. : 0.); (_state == State::Muted || _state == State::RaisedHand) ? 1. : 0.);
const auto mutedByMe = (_state == State::MutedByMe); const auto mutedByMe = (_state == State::MutedByMe);
_delegate->rowPaintIcon(p, iconRect, speaking, active, muted, mutedByMe); _delegate->rowPaintIcon(p, iconRect, {
.speaking = speaking,
.active = active,
.muted = muted,
.mutedByMe = (_state == State::MutedByMe),
.raisedHand = (_state == State::RaisedHand),
});
} }
void Row::refreshStatus() { void Row::refreshStatus() {
@ -766,6 +782,8 @@ void Row::refreshStatus() {
? u"%1% %2"_q ? u"%1% %2"_q
.arg(std::round(_volume / 100.)) .arg(std::round(_volume / 100.))
.arg(tr::lng_group_call_active(tr::now)) .arg(tr::lng_group_call_active(tr::now))
: (_state == State::RaisedHand)
? tr::lng_group_call_raised_hand_status(tr::now)
: tr::lng_group_call_inactive(tr::now)), : tr::lng_group_call_inactive(tr::now)),
_speaking); _speaking);
} }
@ -1260,24 +1278,25 @@ void MembersController::rowUpdateRow(not_null<Row*> row) {
void MembersController::rowPaintIcon( void MembersController::rowPaintIcon(
Painter &p, Painter &p,
QRect rect, QRect rect,
float64 speaking, IconState state) {
float64 active,
float64 muted,
bool mutedByMe) {
const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon; const auto &greenIcon = st::groupCallMemberColoredCrossLine.icon;
const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2; const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2;
const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2; const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2;
if (speaking == 1. && !mutedByMe) { if (state.speaking == 1. && !state.mutedByMe) {
// Just green icon, no cross, no coloring. // Just green icon, no cross, no coloring.
greenIcon.paintInCenter(p, rect); greenIcon.paintInCenter(p, rect);
return; return;
} else if (speaking == 0.) { } else if (state.speaking == 0.) {
if (active == 1.) { if (state.active == 1.) {
// Just gray icon, no cross, no coloring. // Just gray icon, no cross, no coloring.
st::groupCallMemberInactiveCrossLine.icon.paintInCenter(p, rect); st::groupCallMemberInactiveCrossLine.icon.paintInCenter(p, rect);
return; return;
} else if (active == 0.) { } else if (state.active == 0.) {
if (muted == 1.) { if (state.muted == 1.) {
if (state.raisedHand) {
st::groupCallMemberRaisedHand.paintInCenter(p, rect);
return;
}
// Red crossed icon, colorized once, cached as last frame. // Red crossed icon, colorized once, cached as last frame.
_coloredCrossLine.paint( _coloredCrossLine.paint(
p, p,
@ -1286,7 +1305,7 @@ void MembersController::rowPaintIcon(
1., 1.,
st::groupCallMemberMutedIcon->c); st::groupCallMemberMutedIcon->c);
return; return;
} else if (muted == 0.) { } else if (state.muted == 0.) {
// Gray crossed icon, no coloring, cached as last frame. // Gray crossed icon, no coloring, cached as last frame.
_inactiveCrossLine.paint(p, left, top, 1.); _inactiveCrossLine.paint(p, left, top, 1.);
return; return;
@ -1295,17 +1314,18 @@ void MembersController::rowPaintIcon(
} }
const auto activeInactiveColor = anim::color( const auto activeInactiveColor = anim::color(
st::groupCallMemberInactiveIcon, st::groupCallMemberInactiveIcon,
(mutedByMe (state.mutedByMe
? st::groupCallMemberMutedIcon ? st::groupCallMemberMutedIcon
: st::groupCallMemberActiveIcon), : st::groupCallMemberActiveIcon),
speaking); state.speaking);
const auto iconColor = anim::color( const auto iconColor = anim::color(
activeInactiveColor, activeInactiveColor,
st::groupCallMemberMutedIcon, st::groupCallMemberMutedIcon,
muted); state.muted);
// Don't use caching of the last frame, because 'muted' may animate color. // Don't use caching of the last frame,
const auto crossProgress = std::min(1. - active, 0.9999); // because 'muted' may animate color.
const auto crossProgress = std::min(1. - state.active, 0.9999);
_inactiveCrossLine.paint(p, left, top, crossProgress, iconColor); _inactiveCrossLine.paint(p, left, top, crossProgress, iconColor);
} }
@ -1493,6 +1513,7 @@ void MembersController::addMuteActionsToContextMenu(
const auto muteState = row->state(); const auto muteState = row->state();
const auto isMuted = (muteState == Row::State::Muted) const auto isMuted = (muteState == Row::State::Muted)
|| (muteState == Row::State::RaisedHand)
|| (muteState == Row::State::MutedByMe); || (muteState == Row::State::MutedByMe);
auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased(); auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
@ -1553,7 +1574,7 @@ void MembersController::addMuteActionsToContextMenu(
const auto muteAction = [&]() -> QAction* { const auto muteAction = [&]() -> QAction* {
if (muteState == Row::State::Invited if (muteState == Row::State::Invited
|| isMe(participantPeer) || isMe(participantPeer)
|| (muteState == Row::State::Muted || (muteState == Row::State::Inactive
&& participantIsCallAdmin && participantIsCallAdmin
&& _peer->canManageGroupCall())) { && _peer->canManageGroupCall())) {
return nullptr; return nullptr;
@ -1561,6 +1582,7 @@ void MembersController::addMuteActionsToContextMenu(
auto callback = [=] { auto callback = [=] {
const auto state = row->state(); const auto state = row->state();
const auto muted = (state == Row::State::Muted) const auto muted = (state == Row::State::Muted)
|| (state == Row::State::RaisedHand)
|| (state == Row::State::MutedByMe); || (state == Row::State::MutedByMe);
toggleMute(!muted, false); toggleMute(!muted, false);
}; };

View file

@ -301,10 +301,13 @@ void GroupCall::applyParticipantsSlice(
: data.is_muted_by_you(); : data.is_muted_by_you();
const auto onlyMinLoaded = data.is_min() const auto onlyMinLoaded = data.is_min()
&& (!was || was->onlyMinLoaded); && (!was || was->onlyMinLoaded);
const auto raisedHandRating
= data.vraise_hand_rating().value_or_empty();
const auto value = Participant{ const auto value = Participant{
.peer = participantPeer, .peer = participantPeer,
.date = data.vdate().v, .date = data.vdate().v,
.lastActive = lastActive, .lastActive = lastActive,
.raisedHandRating = raisedHandRating,
.ssrc = uint32(data.vsource().v), .ssrc = uint32(data.vsource().v),
.volume = volume, .volume = volume,
.applyVolumeFromMin = applyVolumeFromMin, .applyVolumeFromMin = applyVolumeFromMin,

View file

@ -24,6 +24,7 @@ struct GroupCallParticipant {
not_null<PeerData*> peer; not_null<PeerData*> peer;
TimeId date = 0; TimeId date = 0;
TimeId lastActive = 0; TimeId lastActive = 0;
uint64 raisedHandRating = 0;
uint32 ssrc = 0; uint32 ssrc = 0;
int volume = 0; int volume = 0;
bool applyVolumeFromMin = true; bool applyVolumeFromMin = true;