Add raised hand display in participants list.
This commit is contained in:
		
							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 | 
							
								
								
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1,008 B | 
							
								
								
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand@2x.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand@2x.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand@3x.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Telegram/Resources/icons/calls/group_calls_raised_hand@3x.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.7 KiB | 
|  | @ -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"; | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 John Preston
						John Preston