| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973 |
- /*
- 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 "calls/group/calls_group_members.h"
- #include "calls/group/calls_cover_item.h"
- #include "calls/group/calls_group_call.h"
- #include "calls/group/calls_group_menu.h"
- #include "calls/group/calls_volume_item.h"
- #include "calls/group/calls_group_members_row.h"
- #include "calls/group/calls_group_viewport.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_user.h"
- #include "data/data_peer.h"
- #include "data/data_changes.h"
- #include "data/data_group_call.h"
- #include "data/data_peer_values.h" // Data::CanWriteValue.
- #include "data/data_session.h" // Data::Session::invitedToCallUsers.
- #include "settings/settings_common.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/effects/ripple_animation.h"
- #include "ui/effects/cross_line.h"
- #include "ui/painter.h"
- #include "ui/power_saving.h"
- #include "core/application.h" // Core::App().domain, .activeWindow.
- #include "main/main_domain.h" // Core::App().domain().activate.
- #include "main/main_session.h"
- #include "lang/lang_keys.h"
- #include "info/profile/info_profile_values.h" // Info::Profile::NameValue.
- #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
- #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfo...
- #include "window/window_controller.h" // Controller::sessionController.
- #include "window/window_session_controller.h"
- #include "webrtc/webrtc_video_track.h"
- #include "styles/style_calls.h"
- namespace Calls::Group {
- namespace {
- constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
- using Row = MembersRow;
- } // namespace
- class Members::Controller final
- : public PeerListController
- , public MembersRowDelegate
- , public base::has_weak_ptr {
- public:
- Controller(
- not_null<GroupCall*> call,
- not_null<QWidget*> menuParent,
- PanelMode mode);
- ~Controller();
- using MuteRequest = Group::MuteRequest;
- using VolumeRequest = Group::VolumeRequest;
- Main::Session &session() const override;
- void prepare() override;
- void rowClicked(not_null<PeerListRow*> row) override;
- void rowRightActionClicked(not_null<PeerListRow*> row) override;
- base::unique_qptr<Ui::PopupMenu> rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) override;
- void loadMoreRows() override;
- [[nodiscard]] rpl::producer<int> fullCountValue() const {
- return _fullCount.value();
- }
- [[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
- [[nodiscard]] rpl::producer<VolumeRequest> changeVolumeRequests() const;
- [[nodiscard]] auto kickParticipantRequests() const
- -> rpl::producer<not_null<PeerData*>>;
- Row *findRow(not_null<PeerData*> participantPeer) const;
- void setMode(PanelMode mode);
- bool rowIsMe(not_null<PeerData*> participantPeer) override;
- bool rowCanMuteMembers() override;
- void rowUpdateRow(not_null<Row*> row) override;
- void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) override;
- void rowPaintIcon(
- QPainter &p,
- QRect rect,
- const IconState &state) override;
- int rowPaintStatusIcon(
- QPainter &p,
- int x,
- int y,
- int outerWidth,
- not_null<MembersRow*> row,
- const IconState &state) override;
- bool rowIsNarrow() override;
- void rowShowContextMenu(not_null<PeerListRow*> row) override;
- private:
- [[nodiscard]] std::unique_ptr<Row> createRowForMe();
- [[nodiscard]] std::unique_ptr<Row> createRow(
- const Data::GroupCallParticipant &participant);
- [[nodiscard]] std::unique_ptr<Row> createInvitedRow(
- not_null<PeerData*> participantPeer);
- [[nodiscard]] bool isMe(not_null<PeerData*> participantPeer) const;
- void prepareRows(not_null<Data::GroupCall*> real);
- [[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row);
- void addMuteActionsToContextMenu(
- not_null<Ui::PopupMenu*> menu,
- not_null<PeerData*> participantPeer,
- bool participantIsCallAdmin,
- not_null<Row*> row);
- void setupListChangeViewers();
- void subscribeToChanges(not_null<Data::GroupCall*> real);
- void updateRow(
- const std::optional<Data::GroupCallParticipant> &was,
- const Data::GroupCallParticipant &now);
- void updateRow(
- not_null<Row*> row,
- const std::optional<Data::GroupCallParticipant> &was,
- const Data::GroupCallParticipant *participant);
- void updateRowInSoundingMap(
- not_null<Row*> row,
- bool wasSounding,
- uint32 wasSsrc,
- uint32 wasAdditionalSsrc,
- const Data::GroupCallParticipant *participant);
- void updateRowInSoundingMap(
- not_null<Row*> row,
- bool wasSounding,
- uint32 wasSsrc,
- bool nowSounding,
- uint32 nowSsrc);
- void removeRow(not_null<Row*> row);
- void removeRowFromSoundingMap(not_null<Row*> row);
- void updateRowLevel(not_null<Row*> row, float level);
- void checkRowPosition(not_null<Row*> row);
- [[nodiscard]] bool needToReorder(not_null<Row*> row) const;
- [[nodiscard]] bool allRowsAboveAreSpeaking(not_null<Row*> row) const;
- [[nodiscard]] bool allRowsAboveMoreImportantThanHand(
- not_null<Row*> row,
- uint64 raiseHandRating) const;
- [[nodiscard]] const Data::GroupCallParticipant *findParticipant(
- const std::string &endpoint) const;
- [[nodiscard]] const std::string &computeScreenEndpoint(
- not_null<const Data::GroupCallParticipant*> participant) const;
- [[nodiscard]] const std::string &computeCameraEndpoint(
- not_null<const Data::GroupCallParticipant*> participant) const;
- void showRowMenu(not_null<PeerListRow*> row, bool highlightRow);
- void toggleVideoEndpointActive(
- const VideoEndpoint &endpoint,
- bool active);
- void appendInvitedUsers();
- void scheduleRaisedHandStatusRemove();
- void hideRowsWithVideoExcept(const VideoEndpoint &large);
- void showAllHiddenRows();
- void hideRowWithVideo(const VideoEndpoint &endpoint);
- void showRowWithVideo(const VideoEndpoint &endpoint);
- const not_null<GroupCall*> _call;
- not_null<PeerData*> _peer;
- std::string _largeEndpoint;
- bool _prepared = false;
- rpl::event_stream<MuteRequest> _toggleMuteRequests;
- rpl::event_stream<VolumeRequest> _changeVolumeRequests;
- rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
- rpl::variable<int> _fullCount = 1;
- not_null<QWidget*> _menuParent;
- base::unique_qptr<Ui::PopupMenu> _menu;
- base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
- base::flat_map<PeerListRowId, crl::time> _raisedHandStatusRemoveAt;
- base::Timer _raisedHandStatusRemoveTimer;
- base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
- base::flat_set<not_null<PeerData*>> _cameraActive;
- base::flat_set<not_null<PeerData*>> _screenActive;
- Ui::Animations::Basic _soundingAnimation;
- crl::time _soundingAnimationHideLastTime = 0;
- bool _skipRowLevelUpdate = false;
- PanelMode _mode = PanelMode::Default;
- Ui::CrossLineAnimation _inactiveCrossLine;
- Ui::CrossLineAnimation _coloredCrossLine;
- Ui::CrossLineAnimation _inactiveNarrowCrossLine;
- Ui::CrossLineAnimation _coloredNarrowCrossLine;
- Ui::CrossLineAnimation _videoCrossLine;
- Ui::RoundRect _narrowRoundRectSelected;
- Ui::RoundRect _narrowRoundRect;
- QImage _narrowShadow;
- rpl::lifetime _lifetime;
- };
- Members::Controller::Controller(
- not_null<GroupCall*> call,
- not_null<QWidget*> menuParent,
- PanelMode mode)
- : _call(call)
- , _peer(call->peer())
- , _menuParent(menuParent)
- , _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
- , _mode(mode)
- , _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
- , _coloredCrossLine(st::groupCallMemberColoredCrossLine)
- , _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
- , _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine)
- , _videoCrossLine(st::groupCallVideoCrossLine)
- , _narrowRoundRectSelected(
- ImageRoundRadius::Large,
- st::groupCallMembersBgOver)
- , _narrowRoundRect(ImageRoundRadius::Large, st::groupCallMembersBg) {
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- _inactiveCrossLine.invalidate();
- _coloredCrossLine.invalidate();
- _inactiveNarrowCrossLine.invalidate();
- _coloredNarrowCrossLine.invalidate();
- }, _lifetime);
- rpl::combine(
- PowerSaving::OnValue(PowerSaving::kCalls),
- Core::App().appDeactivatedValue()
- ) | rpl::start_with_next([=](bool disabled, bool deactivated) {
- const auto hide = disabled || deactivated;
- if (!(hide && _soundingAnimationHideLastTime)) {
- _soundingAnimationHideLastTime = hide ? crl::now() : 0;
- }
- for (const auto &[_, row] : _soundingRowBySsrc) {
- if (hide) {
- updateRowLevel(row, 0.);
- }
- row->setSkipLevelUpdate(hide);
- }
- if (!hide && !_soundingAnimation.animating()) {
- _soundingAnimation.start();
- }
- _skipRowLevelUpdate = hide;
- }, _lifetime);
- _soundingAnimation.init([=](crl::time now) {
- if (const auto &last = _soundingAnimationHideLastTime; (last > 0)
- && (now - last >= kBlobsEnterDuration)) {
- _soundingAnimation.stop();
- return false;
- }
- for (const auto &[ssrc, row] : _soundingRowBySsrc) {
- row->updateBlobAnimation(now);
- delegate()->peerListUpdateRow(row);
- }
- return true;
- });
- _peer->session().changes().peerUpdates(
- Data::PeerUpdate::Flag::About
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- if (const auto row = findRow(update.peer)) {
- row->setAbout(update.peer->about());
- }
- }, _lifetime);
- }
- Members::Controller::~Controller() {
- base::take(_menu);
- }
- void Members::Controller::setupListChangeViewers() {
- _call->real(
- ) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
- subscribeToChanges(real);
- }, _lifetime);
- _call->levelUpdates(
- ) | rpl::start_with_next([=](const LevelUpdate &update) {
- const auto i = _soundingRowBySsrc.find(update.ssrc);
- if (i != end(_soundingRowBySsrc)) {
- updateRowLevel(i->second, update.value);
- }
- }, _lifetime);
- _call->videoEndpointLargeValue(
- ) | rpl::start_with_next([=](const VideoEndpoint &large) {
- if (large) {
- hideRowsWithVideoExcept(large);
- } else {
- showAllHiddenRows();
- }
- }, _lifetime);
- _call->videoStreamShownUpdates(
- ) | rpl::filter([=](const VideoStateToggle &update) {
- const auto &large = _call->videoEndpointLarge();
- return large && (update.endpoint != large);
- }) | rpl::start_with_next([=](const VideoStateToggle &update) {
- if (update.value) {
- hideRowWithVideo(update.endpoint);
- } else {
- showRowWithVideo(update.endpoint);
- }
- }, _lifetime);
- _call->rejoinEvents(
- ) | rpl::start_with_next([=](const Group::RejoinEvent &event) {
- const auto guard = gsl::finally([&] {
- delegate()->peerListRefreshRows();
- });
- if (const auto row = findRow(event.wasJoinAs)) {
- removeRow(row);
- }
- if (findRow(event.nowJoinAs)) {
- return;
- } else if (auto row = createRowForMe()) {
- delegate()->peerListAppendRow(std::move(row));
- }
- }, _lifetime);
- }
- void Members::Controller::hideRowsWithVideoExcept(
- const VideoEndpoint &large) {
- auto changed = false;
- auto showLargeRow = true;
- for (const auto &endpoint : _call->shownVideoTracks()) {
- if (endpoint != large) {
- if (const auto row = findRow(endpoint.peer)) {
- if (endpoint.peer == large.peer) {
- showLargeRow = false;
- }
- delegate()->peerListSetRowHidden(row, true);
- changed = true;
- }
- }
- }
- if (const auto row = showLargeRow ? findRow(large.peer) : nullptr) {
- delegate()->peerListSetRowHidden(row, false);
- changed = true;
- }
- if (changed) {
- delegate()->peerListRefreshRows();
- }
- }
- void Members::Controller::showAllHiddenRows() {
- auto shown = false;
- for (const auto &endpoint : _call->shownVideoTracks()) {
- if (const auto row = findRow(endpoint.peer)) {
- delegate()->peerListSetRowHidden(row, false);
- shown = true;
- }
- }
- if (shown) {
- delegate()->peerListRefreshRows();
- }
- }
- void Members::Controller::hideRowWithVideo(const VideoEndpoint &endpoint) {
- if (const auto row = findRow(endpoint.peer)) {
- delegate()->peerListSetRowHidden(row, true);
- delegate()->peerListRefreshRows();
- }
- }
- void Members::Controller::showRowWithVideo(const VideoEndpoint &endpoint) {
- const auto peer = endpoint.peer;
- const auto &large = _call->videoEndpointLarge();
- if (large) {
- for (const auto &endpoint : _call->shownVideoTracks()) {
- if (endpoint != large && endpoint.peer == peer) {
- // Still hidden with another video.
- return;
- }
- }
- }
- if (const auto row = findRow(endpoint.peer)) {
- delegate()->peerListSetRowHidden(row, false);
- delegate()->peerListRefreshRows();
- }
- }
- void Members::Controller::subscribeToChanges(not_null<Data::GroupCall*> real) {
- _fullCount = real->fullCountValue();
- real->participantsReloaded(
- ) | rpl::start_with_next([=] {
- prepareRows(real);
- }, _lifetime);
- using Update = Data::GroupCall::ParticipantUpdate;
- real->participantUpdated(
- ) | rpl::start_with_next([=](const Update &update) {
- Expects(update.was.has_value() || update.now.has_value());
- const auto participantPeer = update.was
- ? update.was->peer
- : update.now->peer;
- if (!update.now) {
- if (const auto row = findRow(participantPeer)) {
- if (isMe(participantPeer)) {
- updateRow(row, update.was, nullptr);
- } else {
- removeRow(row);
- delegate()->peerListRefreshRows();
- }
- }
- } else {
- updateRow(update.was, *update.now);
- }
- }, _lifetime);
- for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
- toggleVideoEndpointActive(endpoint, true);
- }
- _call->videoStreamActiveUpdates(
- ) | rpl::start_with_next([=](const VideoStateToggle &update) {
- toggleVideoEndpointActive(update.endpoint, update.value);
- }, _lifetime);
- if (_prepared) {
- appendInvitedUsers();
- }
- }
- void Members::Controller::toggleVideoEndpointActive(
- const VideoEndpoint &endpoint,
- bool active) {
- const auto toggleOne = [=](
- base::flat_set<not_null<PeerData*>> &set,
- not_null<PeerData*> participantPeer,
- bool active) {
- if ((active && set.emplace(participantPeer).second)
- || (!active && set.remove(participantPeer))) {
- if (_mode == PanelMode::Wide) {
- if (const auto row = findRow(participantPeer)) {
- delegate()->peerListUpdateRow(row);
- }
- }
- }
- };
- const auto &id = endpoint.id;
- const auto participantPeer = endpoint.peer;
- const auto real = _call->lookupReal();
- if (active) {
- if (const auto participant = findParticipant(id)) {
- if (computeCameraEndpoint(participant) == id) {
- toggleOne(_cameraActive, participantPeer, true);
- } else if (computeScreenEndpoint(participant) == id) {
- toggleOne(_screenActive, participantPeer, true);
- }
- }
- } else if (const auto participant = real->participantByPeer(
- participantPeer)) {
- const auto &camera = computeCameraEndpoint(participant);
- const auto &screen = computeScreenEndpoint(participant);
- if (camera == id || camera.empty()) {
- toggleOne(_cameraActive, participantPeer, false);
- }
- if (screen == id || screen.empty()) {
- toggleOne(_screenActive, participantPeer, false);
- }
- } else {
- toggleOne(_cameraActive, participantPeer, false);
- toggleOne(_screenActive, participantPeer, false);
- }
- }
- void Members::Controller::appendInvitedUsers() {
- if (const auto id = _call->id()) {
- for (const auto &user : _peer->owner().invitedToCallUsers(id)) {
- if (auto row = createInvitedRow(user)) {
- delegate()->peerListAppendRow(std::move(row));
- }
- }
- delegate()->peerListRefreshRows();
- }
- using Invite = Data::Session::InviteToCall;
- _peer->owner().invitesToCalls(
- ) | rpl::filter([=](const Invite &invite) {
- return (invite.id == _call->id());
- }) | rpl::start_with_next([=](const Invite &invite) {
- if (auto row = createInvitedRow(invite.user)) {
- delegate()->peerListAppendRow(std::move(row));
- delegate()->peerListRefreshRows();
- }
- }, _lifetime);
- }
- void Members::Controller::updateRow(
- const std::optional<Data::GroupCallParticipant> &was,
- const Data::GroupCallParticipant &now) {
- auto reorderIfInvitedBefore = 0;
- auto checkPosition = (Row*)nullptr;
- auto addedToBottom = (Row*)nullptr;
- if (const auto row = findRow(now.peer)) {
- if (row->state() == Row::State::Invited) {
- reorderIfInvitedBefore = row->absoluteIndex();
- }
- updateRow(row, was, &now);
- if ((now.speaking && (!was || !was->speaking))
- || (now.raisedHandRating != (was ? was->raisedHandRating : 0))
- || (!now.canSelfUnmute && was && was->canSelfUnmute)) {
- checkPosition = row;
- }
- } else if (auto row = createRow(now)) {
- if (row->speaking()) {
- delegate()->peerListPrependRow(std::move(row));
- } else {
- reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
- if (now.raisedHandRating != 0) {
- checkPosition = row.get();
- } else {
- addedToBottom = row.get();
- }
- delegate()->peerListAppendRow(std::move(row));
- }
- delegate()->peerListRefreshRows();
- }
- static constexpr auto kInvited = Row::State::Invited;
- const auto reorder = [&] {
- const auto count = reorderIfInvitedBefore;
- if (count <= 0) {
- return false;
- }
- const auto row = delegate()->peerListRowAt(
- reorderIfInvitedBefore - 1).get();
- return (static_cast<Row*>(row)->state() == kInvited);
- }();
- if (reorder) {
- delegate()->peerListPartitionRows([](const PeerListRow &row) {
- return static_cast<const Row&>(row).state() != kInvited;
- });
- }
- if (checkPosition) {
- checkRowPosition(checkPosition);
- } else if (addedToBottom) {
- const auto real = _call->lookupReal();
- if (real && real->joinedToTop()) {
- const auto proj = [&](const PeerListRow &other) {
- const auto &real = static_cast<const Row&>(other);
- return real.speaking()
- ? 2
- : (&real == addedToBottom)
- ? 1
- : 0;
- };
- delegate()->peerListSortRows([&](
- const PeerListRow &a,
- const PeerListRow &b) {
- return proj(a) > proj(b);
- });
- }
- }
- }
- bool Members::Controller::allRowsAboveAreSpeaking(not_null<Row*> row) const {
- const auto count = delegate()->peerListFullRowsCount();
- for (auto i = 0; i != count; ++i) {
- const auto above = delegate()->peerListRowAt(i);
- if (above == row) {
- // All rows above are speaking.
- return true;
- } else if (!static_cast<Row*>(above.get())->speaking()) {
- break;
- }
- }
- return false;
- }
- bool Members::Controller::allRowsAboveMoreImportantThanHand(
- not_null<Row*> row,
- uint64 raiseHandRating) const {
- Expects(raiseHandRating > 0);
- const auto count = delegate()->peerListFullRowsCount();
- for (auto i = 0; i != count; ++i) {
- const auto above = delegate()->peerListRowAt(i);
- if (above == row) {
- // All rows above are 'more important' than this raised hand.
- return true;
- }
- const auto real = static_cast<Row*>(above.get());
- const auto state = real->state();
- if (state == Row::State::Muted
- || (state == Row::State::RaisedHand
- && real->raisedHandRating() < raiseHandRating)) {
- break;
- }
- }
- return false;
- }
- bool Members::Controller::needToReorder(not_null<Row*> row) const {
- // All reorder cases:
- // - bring speaking up
- // - bring raised hand up
- // - bring muted down
- if (row->speaking()) {
- return !allRowsAboveAreSpeaking(row);
- } else if (!_peer->canManageGroupCall()) {
- // Raising hands reorder participants only for voice chat admins.
- return false;
- }
- const auto rating = row->raisedHandRating();
- if (!rating && row->state() != Row::State::Muted) {
- return false;
- }
- if (rating > 0 && !allRowsAboveMoreImportantThanHand(row, rating)) {
- return true;
- }
- const auto index = row->absoluteIndex();
- if (index + 1 == delegate()->peerListFullRowsCount()) {
- // Last one, can't bring lower.
- return false;
- }
- const auto next = delegate()->peerListRowAt(index + 1);
- const auto state = static_cast<Row*>(next.get())->state();
- if ((state != Row::State::Muted) && (state != Row::State::RaisedHand)) {
- return true;
- }
- if (!rating && static_cast<Row*>(next.get())->raisedHandRating()) {
- return true;
- }
- return false;
- }
- void Members::Controller::checkRowPosition(not_null<Row*> row) {
- if (_menu) {
- // Don't reorder rows while we show the popup menu.
- _menuCheckRowsAfterHidden.emplace(row->peer());
- return;
- } else if (!needToReorder(row)) {
- return;
- }
- // Someone started speaking and has a non-speaking row above him.
- // Or someone raised hand and has force muted above him.
- // Or someone was forced muted and had can_unmute_self below him. Sort.
- static constexpr auto kTop = std::numeric_limits<uint64>::max();
- const auto projForAdmin = [&](const PeerListRow &other) {
- const auto &real = static_cast<const Row&>(other);
- return real.speaking()
- // Speaking 'row' to the top, all other speaking below it.
- ? (&real == row.get() ? kTop : (kTop - 1))
- : (real.raisedHandRating() > 0)
- // Then all raised hands sorted by rating.
- ? real.raisedHandRating()
- : (real.state() == Row::State::Muted)
- // All force muted at the bottom, but 'row' still above others.
- ? (&real == row.get() ? 1ULL : 0ULL)
- // All not force-muted lie between raised hands and speaking.
- : (kTop - 2);
- };
- const auto projForOther = [&](const PeerListRow &other) {
- const auto &real = static_cast<const Row&>(other);
- return real.speaking()
- // Speaking 'row' to the top, all other speaking below it.
- ? (&real == row.get() ? kTop : (kTop - 1))
- : 0ULL;
- };
- using Comparator = Fn<bool(const PeerListRow&, const PeerListRow&)>;
- const auto makeComparator = [&](const auto &proj) -> Comparator {
- return [&](const PeerListRow &a, const PeerListRow &b) {
- return proj(a) > proj(b);
- };
- };
- delegate()->peerListSortRows(_peer->canManageGroupCall()
- ? makeComparator(projForAdmin)
- : makeComparator(projForOther));
- }
- void Members::Controller::updateRow(
- not_null<Row*> row,
- const std::optional<Data::GroupCallParticipant> &was,
- const Data::GroupCallParticipant *participant) {
- const auto wasSounding = row->sounding();
- const auto wasSsrc = was ? was->ssrc : 0;
- const auto wasAdditionalSsrc = was
- ? GetAdditionalAudioSsrc(was->videoParams)
- : 0;
- row->setSkipLevelUpdate(_skipRowLevelUpdate);
- row->updateState(participant);
- const auto wasNoSounding = _soundingRowBySsrc.empty();
- updateRowInSoundingMap(
- row,
- wasSounding,
- wasSsrc,
- wasAdditionalSsrc,
- participant);
- const auto nowNoSounding = _soundingRowBySsrc.empty();
- if (wasNoSounding && !nowNoSounding) {
- _soundingAnimation.start();
- } else if (nowNoSounding && !wasNoSounding) {
- _soundingAnimation.stop();
- }
- delegate()->peerListUpdateRow(row);
- }
- void Members::Controller::updateRowInSoundingMap(
- not_null<Row*> row,
- bool wasSounding,
- uint32 wasSsrc,
- uint32 wasAdditionalSsrc,
- const Data::GroupCallParticipant *participant) {
- const auto nowSounding = row->sounding();
- const auto nowSsrc = participant ? participant->ssrc : 0;
- const auto nowAdditionalSsrc = participant
- ? GetAdditionalAudioSsrc(participant->videoParams)
- : 0;
- updateRowInSoundingMap(row, wasSounding, wasSsrc, nowSounding, nowSsrc);
- updateRowInSoundingMap(
- row,
- wasSounding,
- wasAdditionalSsrc,
- nowSounding,
- nowAdditionalSsrc);
- }
- void Members::Controller::updateRowInSoundingMap(
- not_null<Row*> row,
- bool wasSounding,
- uint32 wasSsrc,
- bool nowSounding,
- uint32 nowSsrc) {
- if (wasSsrc == nowSsrc) {
- if (nowSsrc && nowSounding != wasSounding) {
- if (nowSounding) {
- _soundingRowBySsrc.emplace(nowSsrc, row);
- } else {
- _soundingRowBySsrc.remove(nowSsrc);
- }
- }
- } else {
- _soundingRowBySsrc.remove(wasSsrc);
- if (nowSounding && nowSsrc) {
- _soundingRowBySsrc.emplace(nowSsrc, row);
- }
- }
- }
- void Members::Controller::removeRow(not_null<Row*> row) {
- removeRowFromSoundingMap(row);
- delegate()->peerListRemoveRow(row);
- }
- void Members::Controller::removeRowFromSoundingMap(not_null<Row*> row) {
- // There may be 0, 1 or 2 entries for a row.
- for (auto i = begin(_soundingRowBySsrc); i != end(_soundingRowBySsrc);) {
- if (i->second == row) {
- i = _soundingRowBySsrc.erase(i);
- } else {
- ++i;
- }
- }
- }
- void Members::Controller::updateRowLevel(
- not_null<Row*> row,
- float level) {
- if (_skipRowLevelUpdate) {
- return;
- }
- row->updateLevel(level);
- }
- Row *Members::Controller::findRow(
- not_null<PeerData*> participantPeer) const {
- return static_cast<Row*>(
- delegate()->peerListFindRow(participantPeer->id.value));
- }
- void Members::Controller::setMode(PanelMode mode) {
- _mode = mode;
- }
- const Data::GroupCallParticipant *Members::Controller::findParticipant(
- const std::string &endpoint) const {
- if (endpoint.empty()) {
- return nullptr;
- }
- const auto real = _call->lookupReal();
- if (!real) {
- return nullptr;
- } else if (endpoint == _call->screenSharingEndpoint()
- || endpoint == _call->cameraSharingEndpoint()) {
- return real->participantByPeer(_call->joinAs());
- } else {
- return real->participantByEndpoint(endpoint);
- }
- }
- const std::string &Members::Controller::computeScreenEndpoint(
- not_null<const Data::GroupCallParticipant*> participant) const {
- return (participant->peer == _call->joinAs())
- ? _call->screenSharingEndpoint()
- : participant->screenEndpoint();
- }
- const std::string &Members::Controller::computeCameraEndpoint(
- not_null<const Data::GroupCallParticipant*> participant) const {
- return (participant->peer == _call->joinAs())
- ? _call->cameraSharingEndpoint()
- : participant->cameraEndpoint();
- }
- Main::Session &Members::Controller::session() const {
- return _call->peer()->session();
- }
- void Members::Controller::prepare() {
- delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
- setDescription(nullptr);
- setSearchNoResults(nullptr);
- if (const auto real = _call->lookupReal()) {
- prepareRows(real);
- } else if (auto row = createRowForMe()) {
- delegate()->peerListAppendRow(std::move(row));
- delegate()->peerListRefreshRows();
- }
- loadMoreRows();
- appendInvitedUsers();
- _prepared = true;
- setupListChangeViewers();
- }
- bool Members::Controller::isMe(not_null<PeerData*> participantPeer) const {
- return (_call->joinAs() == participantPeer);
- }
- void Members::Controller::prepareRows(not_null<Data::GroupCall*> real) {
- auto foundMe = false;
- auto changed = false;
- auto count = delegate()->peerListFullRowsCount();
- for (auto i = 0; i != count;) {
- const auto row = static_cast<Row*>(
- delegate()->peerListRowAt(i).get());
- removeRowFromSoundingMap(row);
- const auto participantPeer = row->peer();
- const auto me = isMe(participantPeer);
- if (me) {
- foundMe = true;
- }
- if (const auto found = real->participantByPeer(participantPeer)) {
- updateRowInSoundingMap(row, false, 0, 0, found);
- ++i;
- } else if (me) {
- ++i;
- } else {
- changed = true;
- removeRow(row);
- --count;
- }
- }
- if (!foundMe) {
- const auto me = _call->joinAs();
- const auto participant = real->participantByPeer(me);
- auto row = participant
- ? createRow(*participant)
- : createRowForMe();
- if (row) {
- changed = true;
- delegate()->peerListAppendRow(std::move(row));
- }
- }
- for (const auto &participant : real->participants()) {
- if (auto row = createRow(participant)) {
- changed = true;
- delegate()->peerListAppendRow(std::move(row));
- }
- }
- if (changed) {
- delegate()->peerListRefreshRows();
- }
- }
- void Members::Controller::loadMoreRows() {
- if (const auto real = _call->lookupReal()) {
- real->requestParticipants();
- }
- }
- auto Members::Controller::toggleMuteRequests() const
- -> rpl::producer<MuteRequest> {
- return _toggleMuteRequests.events();
- }
- auto Members::Controller::changeVolumeRequests() const
- -> rpl::producer<VolumeRequest> {
- return _changeVolumeRequests.events();
- }
- bool Members::Controller::rowIsMe(not_null<PeerData*> participantPeer) {
- return isMe(participantPeer);
- }
- bool Members::Controller::rowCanMuteMembers() {
- return _peer->canManageGroupCall();
- }
- void Members::Controller::rowUpdateRow(not_null<Row*> row) {
- delegate()->peerListUpdateRow(row);
- }
- void Members::Controller::rowScheduleRaisedHandStatusRemove(
- not_null<Row*> row) {
- const auto id = row->id();
- const auto when = crl::now() + kKeepRaisedHandStatusDuration;
- const auto i = _raisedHandStatusRemoveAt.find(id);
- if (i != _raisedHandStatusRemoveAt.end()) {
- i->second = when;
- } else {
- _raisedHandStatusRemoveAt.emplace(id, when);
- }
- scheduleRaisedHandStatusRemove();
- }
- void Members::Controller::scheduleRaisedHandStatusRemove() {
- auto waiting = crl::time(0);
- const auto now = crl::now();
- for (auto i = begin(_raisedHandStatusRemoveAt)
- ; i != end(_raisedHandStatusRemoveAt);) {
- if (i->second <= now) {
- if (const auto row = delegate()->peerListFindRow(i->first)) {
- static_cast<Row*>(row)->clearRaisedHandStatus();
- }
- i = _raisedHandStatusRemoveAt.erase(i);
- } else {
- if (!waiting || waiting > (i->second - now)) {
- waiting = i->second - now;
- }
- ++i;
- }
- }
- if (waiting > 0) {
- if (!_raisedHandStatusRemoveTimer.isActive()
- || _raisedHandStatusRemoveTimer.remainingTime() > waiting) {
- _raisedHandStatusRemoveTimer.callOnce(waiting);
- }
- }
- }
- void Members::Controller::rowPaintIcon(
- QPainter &p,
- QRect rect,
- const IconState &state) {
- if (_mode == PanelMode::Wide
- && state.style == MembersRowStyle::Default) {
- return;
- }
- const auto narrow = (state.style == MembersRowStyle::Narrow);
- if (state.invited) {
- if (narrow) {
- st::groupCallNarrowInvitedIcon.paintInCenter(p, rect);
- } else {
- st::groupCallMemberInvited.paintInCenter(
- p,
- QRect(
- rect.topLeft() + st::groupCallMemberInvitedPosition,
- st::groupCallMemberInvited.size()));
- }
- return;
- }
- const auto video = (state.style == MembersRowStyle::Video);
- const auto &greenIcon = video
- ? st::groupCallVideoCrossLine.icon
- : narrow
- ? st::groupCallNarrowColoredCrossLine.icon
- : st::groupCallMemberColoredCrossLine.icon;
- const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2;
- const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2;
- if (state.speaking == 1. && !state.mutedByMe) {
- // Just green icon, no cross, no coloring.
- greenIcon.paintInCenter(p, rect);
- return;
- } else if (state.speaking == 0. && (!narrow || !state.mutedByMe)) {
- if (state.active == 1.) {
- // Just gray icon, no cross, no coloring.
- const auto &grayIcon = video
- ? st::groupCallVideoCrossLine.icon
- : narrow
- ? st::groupCallNarrowInactiveCrossLine.icon
- : st::groupCallMemberInactiveCrossLine.icon;
- grayIcon.paintInCenter(p, rect);
- return;
- } else if (state.active == 0.) {
- if (state.muted == 1.) {
- if (state.raisedHand) {
- (narrow
- ? st::groupCallNarrowRaisedHand
- : st::groupCallMemberRaisedHand).paintInCenter(p, rect);
- return;
- }
- // Red crossed icon, colorized once, cached as last frame.
- auto &line = video
- ? _videoCrossLine
- : narrow
- ? _coloredNarrowCrossLine
- : _coloredCrossLine;
- const auto color = video
- ? std::nullopt
- : std::make_optional(st::groupCallMemberMutedIcon->c);
- line.paint(
- p,
- left,
- top,
- 1.,
- color);
- return;
- } else if (state.muted == 0.) {
- // Gray crossed icon, no coloring, cached as last frame.
- auto &line = video
- ? _videoCrossLine
- : narrow
- ? _inactiveNarrowCrossLine
- : _inactiveCrossLine;
- line.paint(p, left, top, 1.);
- return;
- }
- }
- }
- const auto activeInactiveColor = anim::color(
- (narrow
- ? st::groupCallMemberNotJoinedStatus
- : st::groupCallMemberInactiveIcon),
- (narrow
- ? st::groupCallMemberActiveStatus
- : state.mutedByMe
- ? st::groupCallMemberMutedIcon
- : st::groupCallMemberActiveIcon),
- state.speaking);
- const auto iconColor = anim::color(
- activeInactiveColor,
- st::groupCallMemberMutedIcon,
- state.muted);
- const auto color = video
- ? std::nullopt
- : std::make_optional((narrow && state.mutedByMe)
- ? st::groupCallMemberMutedIcon->c
- : (narrow && state.raisedHand)
- ? st::groupCallMemberInactiveStatus->c
- : iconColor);
- // Don't use caching of the last frame,
- // because 'muted' may animate color.
- const auto crossProgress = std::min(1. - state.active, 0.9999);
- auto &line = video
- ? _videoCrossLine
- : narrow
- ? _inactiveNarrowCrossLine
- : _inactiveCrossLine;
- line.paint(p, left, top, crossProgress, color);
- }
- int Members::Controller::rowPaintStatusIcon(
- QPainter &p,
- int x,
- int y,
- int outerWidth,
- not_null<MembersRow*> row,
- const IconState &state) {
- Expects(state.style == MembersRowStyle::Narrow);
- if (_mode != PanelMode::Wide) {
- return 0;
- }
- const auto &icon = st::groupCallNarrowColoredCrossLine.icon;
- x += st::groupCallNarrowIconPosition.x();
- y += st::groupCallNarrowIconPosition.y();
- const auto rect = QRect(x, y, icon.width(), icon.height());
- rowPaintIcon(p, rect, state);
- x += icon.width();
- auto result = st::groupCallNarrowIconSkip;
- const auto participantPeer = row->peer();
- const auto camera = _cameraActive.contains(participantPeer);
- const auto screen = _screenActive.contains(participantPeer);
- if (camera || screen) {
- const auto activeInactiveColor = anim::color(
- st::groupCallMemberNotJoinedStatus,
- st::groupCallMemberActiveStatus,
- state.speaking);
- const auto iconColor = anim::color(
- activeInactiveColor,
- st::groupCallMemberNotJoinedStatus,
- state.muted);
- const auto other = state.mutedByMe
- ? st::groupCallMemberMutedIcon->c
- : state.raisedHand
- ? st::groupCallMemberInactiveStatus->c
- : iconColor;
- if (camera) {
- st::groupCallNarrowCameraIcon.paint(p, x, y, outerWidth, other);
- x += st::groupCallNarrowCameraIcon.width();
- result += st::groupCallNarrowCameraIcon.width();
- }
- if (screen) {
- st::groupCallNarrowScreenIcon.paint(p, x, y, outerWidth, other);
- x += st::groupCallNarrowScreenIcon.width();
- result += st::groupCallNarrowScreenIcon.width();
- }
- }
- return result;
- }
- bool Members::Controller::rowIsNarrow() {
- return (_mode == PanelMode::Wide);
- }
- void Members::Controller::rowShowContextMenu(not_null<PeerListRow*> row) {
- showRowMenu(row, false);
- }
- auto Members::Controller::kickParticipantRequests() const
- -> rpl::producer<not_null<PeerData*>>{
- return _kickParticipantRequests.events();
- }
- void Members::Controller::rowClicked(not_null<PeerListRow*> row) {
- showRowMenu(row, true);
- }
- void Members::Controller::showRowMenu(
- not_null<PeerListRow*> row,
- bool highlightRow) {
- const auto cleanup = [=](not_null<Ui::PopupMenu*> menu) {
- if (!_menu || _menu.get() != menu) {
- return;
- }
- auto saved = base::take(_menu);
- for (const auto &peer : base::take(_menuCheckRowsAfterHidden)) {
- if (const auto row = findRow(peer)) {
- checkRowPosition(row);
- }
- }
- _menu = std::move(saved);
- };
- delegate()->peerListShowRowMenu(row, highlightRow, cleanup);
- }
- void Members::Controller::rowRightActionClicked(
- not_null<PeerListRow*> row) {
- showRowMenu(row, true);
- }
- base::unique_qptr<Ui::PopupMenu> Members::Controller::rowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) {
- auto result = createRowContextMenu(parent, row);
- if (result) {
- // First clear _menu value, so that we don't check row positions yet.
- base::take(_menu);
- // Here unique_qptr is used like a shared pointer, where
- // not the last destroyed pointer destroys the object, but the first.
- _menu = base::unique_qptr<Ui::PopupMenu>(result.get());
- }
- return result;
- }
- base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
- QWidget *parent,
- not_null<PeerListRow*> row) {
- const auto participantPeer = row->peer();
- const auto real = static_cast<Row*>(row.get());
- const auto muteState = real->state();
- const auto muted = (muteState == Row::State::Muted)
- || (muteState == Row::State::RaisedHand);
- const auto addCover = !_call->rtmp();
- const auto addVolumeItem = (!muted || isMe(participantPeer));
- const auto admin = IsGroupCallAdmin(_peer, participantPeer);
- const auto session = &_peer->session();
- const auto account = &session->account();
- auto result = base::make_unique_q<Ui::PopupMenu>(
- parent,
- (addCover
- ? st::groupCallPopupMenuWithCover
- : addVolumeItem
- ? st::groupCallPopupMenuWithVolume
- : st::groupCallPopupMenu));
- const auto weakMenu = Ui::MakeWeak(result.get());
- const auto withActiveWindow = [=](auto callback) {
- if (const auto window = Core::App().activePrimaryWindow()) {
- if (const auto menu = weakMenu.data()) {
- menu->discardParentReActivate();
- // We must hide PopupMenu before we activate the MainWindow,
- // otherwise we set focus in field inside MainWindow and then
- // PopupMenu::hide activates back the group call panel :(
- delete weakMenu;
- }
- window->invokeForSessionController(
- account,
- participantPeer,
- [&](not_null<Window::SessionController*> newController) {
- callback(newController);
- newController->widget()->activate();
- });
- }
- };
- const auto showProfile = [=] {
- withActiveWindow([=](not_null<Window::SessionController*> window) {
- window->showPeerInfo(participantPeer);
- });
- };
- const auto showHistory = [=] {
- withActiveWindow([=](not_null<Window::SessionController*> window) {
- window->showPeerHistory(
- participantPeer,
- Window::SectionShow::Way::Forward);
- });
- };
- const auto removeFromVoiceChat = crl::guard(this, [=] {
- _kickParticipantRequests.fire_copy(participantPeer);
- });
- if (addCover) {
- result->addAction(base::make_unique_q<CoverItem>(
- result->menu(),
- st::groupCallPopupCoverMenu,
- st::groupCallMenuCover,
- Info::Profile::NameValue(participantPeer),
- PrepareShortInfoStatus(participantPeer),
- PrepareShortInfoUserpic(
- participantPeer,
- st::groupCallMenuCover)));
- if (const auto about = participantPeer->about(); !about.isEmpty()) {
- result->addAction(base::make_unique_q<AboutItem>(
- result->menu(),
- st::groupCallPopupCoverMenu,
- Info::Profile::AboutWithEntities(participantPeer, about)));
- }
- }
- if (const auto real = _call->lookupReal()) {
- auto oneFound = false;
- auto hasTwoOrMore = false;
- const auto &shown = _call->shownVideoTracks();
- for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
- if (shown.contains(endpoint)) {
- if (oneFound) {
- hasTwoOrMore = true;
- break;
- }
- oneFound = true;
- }
- }
- const auto participant = real->participantByPeer(participantPeer);
- if (participant && hasTwoOrMore) {
- const auto &large = _call->videoEndpointLarge();
- const auto pinned = _call->videoEndpointPinned();
- const auto camera = VideoEndpoint{
- VideoEndpointType::Camera,
- participantPeer,
- computeCameraEndpoint(participant),
- };
- const auto screen = VideoEndpoint{
- VideoEndpointType::Screen,
- participantPeer,
- computeScreenEndpoint(participant),
- };
- if (shown.contains(camera)) {
- if (pinned && large == camera) {
- result->addAction(
- tr::lng_group_call_context_unpin_camera(tr::now),
- [=] { _call->pinVideoEndpoint({}); });
- } else {
- result->addAction(
- tr::lng_group_call_context_pin_camera(tr::now),
- [=] { _call->pinVideoEndpoint(camera); });
- }
- }
- if (shown.contains(screen)) {
- if (pinned && large == screen) {
- result->addAction(
- tr::lng_group_call_context_unpin_screen(tr::now),
- [=] { _call->pinVideoEndpoint({}); });
- } else {
- result->addAction(
- tr::lng_group_call_context_pin_screen(tr::now),
- [=] { _call->pinVideoEndpoint(screen); });
- }
- }
- }
- if (_call->rtmp()) {
- addMuteActionsToContextMenu(
- result,
- row->peer(),
- false,
- static_cast<Row*>(row.get()));
- } else if (participant
- && (!isMe(participantPeer) || _peer->canManageGroupCall())
- && (participant->ssrc != 0
- || GetAdditionalAudioSsrc(participant->videoParams) != 0)) {
- addMuteActionsToContextMenu(
- result,
- participantPeer,
- admin,
- static_cast<Row*>(row.get()));
- }
- }
- if (isMe(participantPeer)) {
- if (_call->muted() == MuteState::RaisedHand) {
- const auto removeHand = [=] {
- if (_call->muted() == MuteState::RaisedHand) {
- _call->setMutedAndUpdate(MuteState::ForceMuted);
- }
- };
- result->addAction(
- tr::lng_group_call_context_remove_hand(tr::now),
- removeHand);
- }
- } else {
- result->addAction(
- (participantPeer->isUser()
- ? tr::lng_context_view_profile(tr::now)
- : participantPeer->isBroadcast()
- ? tr::lng_context_view_channel(tr::now)
- : tr::lng_context_view_group(tr::now)),
- showProfile);
- if (participantPeer->isUser()) {
- result->addAction(
- tr::lng_context_send_message(tr::now),
- showHistory);
- }
- const auto canKick = [&] {
- const auto user = participantPeer->asUser();
- if (static_cast<Row*>(row.get())->state()
- == Row::State::Invited) {
- return false;
- } else if (const auto chat = _peer->asChat()) {
- return chat->amCreator()
- || (user
- && chat->canBanMembers()
- && !chat->admins.contains(user));
- } else if (const auto channel = _peer->asChannel()) {
- return !participantPeer->isMegagroup() // That's the creator.
- && channel->canRestrictParticipant(participantPeer);
- }
- return false;
- }();
- if (canKick) {
- result->addAction(MakeAttentionAction(
- result->menu(),
- tr::lng_group_call_context_remove(tr::now),
- removeFromVoiceChat));
- }
- }
- if (result->actions().size() < (addCover ? 2 : 1)) {
- return nullptr;
- }
- return result;
- }
- void Members::Controller::addMuteActionsToContextMenu(
- not_null<Ui::PopupMenu*> menu,
- not_null<PeerData*> participantPeer,
- bool participantIsCallAdmin,
- not_null<Row*> row) {
- const auto muteUnmuteString = [=](bool muted, bool mutedByMe) {
- return (muted && _peer->canManageGroupCall())
- ? tr::lng_group_call_context_unmute(tr::now)
- : mutedByMe
- ? tr::lng_group_call_context_unmute_for_me(tr::now)
- : _peer->canManageGroupCall()
- ? tr::lng_group_call_context_mute(tr::now)
- : tr::lng_group_call_context_mute_for_me(tr::now);
- };
- const auto toggleMute = crl::guard(this, [=](bool mute, bool local) {
- _toggleMuteRequests.fire(Group::MuteRequest{
- .peer = participantPeer,
- .mute = mute,
- .locallyOnly = local,
- });
- });
- const auto changeVolume = crl::guard(this, [=](
- int volume,
- bool local) {
- _changeVolumeRequests.fire(Group::VolumeRequest{
- .peer = participantPeer,
- .volume = std::clamp(volume, 1, Group::kMaxVolume),
- .locallyOnly = local,
- });
- });
- const auto muteState = row->state();
- const auto muted = (muteState == Row::State::Muted)
- || (muteState == Row::State::RaisedHand);
- const auto mutedByMe = row->mutedByMe();
- auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
- const auto addVolumeItem = (!muted || isMe(participantPeer));
- if (addVolumeItem) {
- auto otherParticipantStateValue
- = _call->otherParticipantStateValue(
- ) | rpl::filter([=](const Group::ParticipantState &data) {
- return data.peer == participantPeer;
- });
- auto volumeItem = base::make_unique_q<MenuVolumeItem>(
- menu->menu(),
- st::groupCallPopupVolumeMenu,
- st::groupCallMenuVolumeSlider,
- otherParticipantStateValue,
- _call->rtmp() ? _call->rtmpVolume() : row->volume(),
- Group::kMaxVolume,
- muted,
- st::groupCallMenuVolumePadding);
- mutesFromVolume = volumeItem->toggleMuteRequests();
- volumeItem->toggleMuteRequests(
- ) | rpl::start_with_next([=](bool muted) {
- if (muted) {
- // Slider value is changed after the callback is called.
- // To capture good state inside the slider frame we postpone.
- crl::on_main(menu, [=] {
- menu->hideMenu();
- });
- }
- toggleMute(muted, false);
- }, volumeItem->lifetime());
- volumeItem->toggleMuteLocallyRequests(
- ) | rpl::start_with_next([=](bool muted) {
- if (!isMe(participantPeer)) {
- toggleMute(muted, true);
- }
- }, volumeItem->lifetime());
- volumeItem->changeVolumeRequests(
- ) | rpl::start_with_next([=](int volume) {
- changeVolume(volume, false);
- }, volumeItem->lifetime());
- volumeItem->changeVolumeLocallyRequests(
- ) | rpl::start_with_next([=](int volume) {
- if (!isMe(participantPeer)) {
- changeVolume(volume, true);
- }
- }, volumeItem->lifetime());
- if (menu->actions().size() > 1) { // First - cover.
- menu->addSeparator();
- }
- menu->addAction(std::move(volumeItem));
- if (!_call->rtmp() && !isMe(participantPeer)) {
- menu->addSeparator();
- }
- };
- const auto muteAction = [&]() -> QAction* {
- if (muteState == Row::State::Invited
- || _call->rtmp()
- || isMe(participantPeer)
- || (muteState == Row::State::Inactive
- && participantIsCallAdmin
- && _peer->canManageGroupCall())) {
- return nullptr;
- }
- auto callback = [=] {
- const auto state = row->state();
- const auto muted = (state == Row::State::Muted)
- || (state == Row::State::RaisedHand);
- const auto mutedByMe = row->mutedByMe();
- toggleMute(!mutedByMe && (!_call->canManage() || !muted), false);
- };
- return menu->addAction(
- muteUnmuteString(muted, mutedByMe),
- std::move(callback));
- }();
- if (muteAction) {
- std::move(
- mutesFromVolume
- ) | rpl::start_with_next([=](bool mutedFromVolume) {
- const auto state = _call->canManage()
- ? (mutedFromVolume
- ? (row->raisedHandRating()
- ? Row::State::RaisedHand
- : Row::State::Muted)
- : Row::State::Inactive)
- : row->state();
- const auto muted = (state == Row::State::Muted)
- || (state == Row::State::RaisedHand);
- const auto mutedByMe = _call->canManage()
- ? false
- : mutedFromVolume;
- muteAction->setText(muteUnmuteString(muted, mutedByMe));
- }, menu->lifetime());
- }
- }
- std::unique_ptr<Row> Members::Controller::createRowForMe() {
- auto result = std::make_unique<Row>(this, _call->joinAs());
- updateRow(result.get(), std::nullopt, nullptr);
- return result;
- }
- std::unique_ptr<Row> Members::Controller::createRow(
- const Data::GroupCallParticipant &participant) {
- auto result = std::make_unique<Row>(this, participant.peer);
- updateRow(result.get(), std::nullopt, &participant);
- return result;
- }
- std::unique_ptr<Row> Members::Controller::createInvitedRow(
- not_null<PeerData*> participantPeer) {
- if (findRow(participantPeer)) {
- return nullptr;
- }
- auto result = std::make_unique<Row>(this, participantPeer);
- updateRow(result.get(), std::nullopt, nullptr);
- return result;
- }
- Members::Members(
- not_null<QWidget*> parent,
- not_null<GroupCall*> call,
- PanelMode mode,
- Ui::GL::Backend backend)
- : RpWidget(parent)
- , _call(call)
- , _mode(mode)
- , _scroll(this)
- , _listController(std::make_unique<Controller>(call, parent, mode))
- , _layout(_scroll->setOwnedWidget(
- object_ptr<Ui::VerticalLayout>(_scroll.data())))
- , _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
- , _viewport(
- std::make_unique<Viewport>(
- _videoWrap.get(),
- PanelMode::Default,
- backend)) {
- setupList();
- setupAddMember(call);
- setContent(_list);
- setupFakeRoundCorners();
- _listController->setDelegate(static_cast<PeerListDelegate*>(this));
- trackViewportGeometry();
- }
- Members::~Members() {
- _viewport = nullptr;
- }
- auto Members::toggleMuteRequests() const
- -> rpl::producer<Group::MuteRequest> {
- return _listController->toggleMuteRequests();
- }
- auto Members::changeVolumeRequests() const
- -> rpl::producer<Group::VolumeRequest> {
- return _listController->changeVolumeRequests();
- }
- auto Members::kickParticipantRequests() const
- -> rpl::producer<not_null<PeerData*>> {
- return _listController->kickParticipantRequests();
- }
- not_null<Viewport*> Members::viewport() const {
- return _viewport.get();
- }
- int Members::desiredHeight() const {
- const auto count = [&] {
- if (const auto real = _call->lookupReal()) {
- return real->fullCount();
- }
- return 0;
- }();
- const auto use = std::max(count, _list->fullRowsCount());
- const auto single = st::groupCallMembersList.item.height;
- const auto desired = (_layout->height() - _list->height())
- + (use * single)
- + (use ? st::lineWidth : 0);
- return std::max(height(), desired);
- }
- rpl::producer<int> Members::desiredHeightValue() const {
- return rpl::combine(
- heightValue(),
- _addMemberButton.value(),
- _listController->fullCountValue(),
- _mode.value()
- ) | rpl::map([=] {
- return desiredHeight();
- });
- }
- void Members::setupAddMember(not_null<GroupCall*> call) {
- using namespace rpl::mappers;
- const auto peer = call->peer();
- const auto canAddByPeer = [=](not_null<PeerData*> peer) {
- if (peer->isBroadcast()) {
- return rpl::single(false) | rpl::type_erased();
- }
- return rpl::combine(
- Data::CanSendValue(peer, ChatRestriction::SendOther, false),
- _call->joinAsValue()
- ) | rpl::map([=](bool can, not_null<PeerData*> joinAs) {
- return can && joinAs->isSelf();
- }) | rpl::type_erased();
- };
- const auto canInviteByLinkByPeer = [=](not_null<PeerData*> peer) {
- const auto channel = peer->asChannel();
- if (!channel) {
- return rpl::single(false) | rpl::type_erased();
- }
- return rpl::single(
- false
- ) | rpl::then(_call->real(
- ) | rpl::map([=] {
- return Data::PeerFlagValue(
- channel,
- ChannelDataFlag::Username);
- }) | rpl::flatten_latest()) | rpl::type_erased();
- };
- _canAddMembers = canAddByPeer(peer);
- _canInviteByLink = canInviteByLinkByPeer(peer);
- SubscribeToMigration(
- peer,
- lifetime(),
- [=](not_null<ChannelData*> channel) {
- _canAddMembers = canAddByPeer(channel);
- _canInviteByLink = canInviteByLinkByPeer(channel);
- });
- rpl::combine(
- _canAddMembers.value(),
- _canInviteByLink.value(),
- _mode.value()
- ) | rpl::start_with_next([=](bool add, bool invite, PanelMode mode) {
- if (!add && !invite) {
- if (const auto old = _addMemberButton.current()) {
- delete old;
- _addMemberButton = nullptr;
- updateControlsGeometry();
- }
- return;
- }
- auto addMember = Settings::CreateButtonWithIcon(
- _layout.get(),
- tr::lng_group_call_invite(),
- st::groupCallAddMember,
- { .icon = &st::groupCallAddMemberIcon });
- addMember->clicks(
- ) | rpl::to_empty | rpl::start_to_stream(
- _addMemberRequests,
- addMember->lifetime());
- addMember->show();
- addMember->resizeToWidth(_layout->width());
- delete _addMemberButton.current();
- _addMemberButton = addMember.data();
- _layout->insert(3, std::move(addMember));
- }, lifetime());
- updateControlsGeometry();
- }
- Row *Members::lookupRow(not_null<PeerData*> peer) const {
- return _listController->findRow(peer);
- }
- not_null<MembersRow*> Members::rtmpFakeRow(not_null<PeerData*> peer) const {
- if (!_rtmpFakeRow) {
- _rtmpFakeRow = std::make_unique<Row>(_listController.get(), peer);
- }
- return _rtmpFakeRow.get();
- }
- void Members::setMode(PanelMode mode) {
- if (_mode.current() == mode) {
- return;
- }
- _mode = mode;
- _listController->setMode(mode);
- }
- QRect Members::getInnerGeometry() const {
- const auto addMembers = _addMemberButton.current();
- const auto add = addMembers ? addMembers->height() : 0;
- return QRect(
- 0,
- -_scroll->scrollTop(),
- width(),
- _list->y() + _list->height() + _bottomSkip->height() + add);
- }
- rpl::producer<int> Members::fullCountValue() const {
- return _listController->fullCountValue();
- }
- void Members::setupList() {
- _listController->setStyleOverrides(&st::groupCallMembersList);
- const auto addSkip = [&] {
- const auto result = _layout->add(
- object_ptr<Ui::FixedHeightWidget>(
- _layout.get(),
- st::groupCallMembersTopSkip));
- result->paintRequest(
- ) | rpl::start_with_next([=](QRect clip) {
- QPainter(result).fillRect(clip, st::groupCallMembersBg);
- }, result->lifetime());
- return result;
- };
- _topSkip = addSkip();
- _list = _layout->add(
- object_ptr<ListWidget>(
- _layout.get(),
- _listController.get()));
- _bottomSkip = addSkip();
- using namespace rpl::mappers;
- rpl::combine(
- _list->heightValue() | rpl::map(_1 > 0),
- _addMemberButton.value() | rpl::map(_1 != nullptr)
- ) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool hasList, bool hasAddMembers) {
- _topSkip->resize(
- _topSkip->width(),
- hasList ? st::groupCallMembersTopSkip : 0);
- _bottomSkip->resize(
- _bottomSkip->width(),
- (hasList && !hasAddMembers) ? st::groupCallMembersTopSkip : 0);
- }, _list->lifetime());
- const auto skip = _layout->add(object_ptr<Ui::RpWidget>(_layout.get()));
- _mode.value(
- ) | rpl::start_with_next([=](PanelMode mode) {
- skip->resize(skip->width(), (mode == PanelMode::Default)
- ? st::groupCallMembersBottomSkip
- : 0);
- }, skip->lifetime());
- rpl::combine(
- _mode.value(),
- _layout->heightValue()
- ) | rpl::start_with_next([=] {
- resizeToList();
- }, _layout->lifetime());
- rpl::combine(
- _scroll->scrollTopValue(),
- _scroll->heightValue()
- ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) {
- _layout->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight);
- }, _scroll->lifetime());
- }
- void Members::trackViewportGeometry() {
- _call->videoEndpointLargeValue(
- ) | rpl::start_with_next([=](const VideoEndpoint &large) {
- _viewport->showLarge(large);
- }, _viewport->lifetime());
- const auto move = [=] {
- const auto maxTop = _viewport->fullHeight()
- - _viewport->widget()->height();
- if (maxTop < 0) {
- return;
- }
- const auto scrollTop = _scroll->scrollTop();
- const auto shift = std::min(scrollTop, maxTop);
- _viewport->setScrollTop(shift);
- if (_viewport->widget()->y() != shift) {
- _viewport->widget()->move(0, shift);
- }
- };
- const auto resize = [=] {
- _viewport->widget()->resize(
- _layout->width(),
- std::min(_scroll->height(), _viewport->fullHeight()));
- };
- _layout->widthValue(
- ) | rpl::start_with_next([=](int width) {
- _viewport->resizeToWidth(width);
- resize();
- }, _viewport->lifetime());
- _scroll->heightValue(
- ) | rpl::skip(1) | rpl::start_with_next(resize, _viewport->lifetime());
- _scroll->scrollTopValue(
- ) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
- _viewport->fullHeightValue(
- ) | rpl::start_with_next([=](int viewport) {
- _videoWrap->resize(_videoWrap->width(), viewport);
- if (viewport > 0) {
- move();
- resize();
- }
- }, _viewport->lifetime());
- }
- void Members::resizeEvent(QResizeEvent *e) {
- updateControlsGeometry();
- }
- void Members::resizeToList() {
- if (!_list) {
- return;
- }
- const auto newHeight = (_list->height() > 0)
- ? (_layout->height() + st::lineWidth)
- : 0;
- if (height() == newHeight) {
- updateControlsGeometry();
- } else {
- resize(width(), newHeight);
- }
- }
- void Members::updateControlsGeometry() {
- _scroll->setGeometry(rect());
- _layout->resizeToWidth(width());
- }
- void Members::setupFakeRoundCorners() {
- const auto size = st::roundRadiusLarge;
- const auto full = 3 * size;
- const auto imagePartSize = size * style::DevicePixelRatio();
- const auto imageSize = full * style::DevicePixelRatio();
- const auto image = std::make_shared<QImage>(
- QImage(imageSize, imageSize, QImage::Format_ARGB32_Premultiplied));
- image->setDevicePixelRatio(style::DevicePixelRatio());
- const auto refreshImage = [=] {
- image->fill(st::groupCallBg->c);
- {
- QPainter p(image.get());
- PainterHighQualityEnabler hq(p);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.setPen(Qt::NoPen);
- p.setBrush(Qt::transparent);
- p.drawRoundedRect(0, 0, full, full, size, size);
- }
- };
- const auto create = [&](QPoint imagePartOrigin) {
- const auto result = Ui::CreateChild<Ui::RpWidget>(_layout.get());
- result->show();
- result->resize(size, size);
- result->setAttribute(Qt::WA_TransparentForMouseEvents);
- result->paintRequest(
- ) | rpl::start_with_next([=] {
- QPainter(result).drawImage(
- result->rect(),
- *image,
- QRect(imagePartOrigin, QSize(imagePartSize, imagePartSize)));
- }, result->lifetime());
- result->raise();
- return result;
- };
- const auto shift = imageSize - imagePartSize;
- const auto topleft = create({ 0, 0 });
- const auto topright = create({ shift, 0 });
- const auto bottomleft = create({ 0, shift });
- const auto bottomright = create({ shift, shift });
- rpl::combine(
- _list->geometryValue(),
- _addMemberButton.value() | rpl::map([=](Ui::RpWidget *widget) {
- topleft->raise();
- topright->raise();
- bottomleft->raise();
- bottomright->raise();
- return widget ? widget->heightValue() : rpl::single(0);
- }) | rpl::flatten_latest()
- ) | rpl::start_with_next([=](QRect list, int addMembers) {
- const auto left = list.x();
- const auto top = list.y() - _topSkip->height();
- const auto right = left + list.width() - topright->width();
- const auto bottom = top
- + _topSkip->height()
- + list.height()
- + _bottomSkip->height()
- + addMembers
- - bottomleft->height();
- topleft->move(left, top);
- topright->move(right, top);
- bottomleft->move(left, bottom);
- bottomright->move(right, bottom);
- }, lifetime());
- refreshImage();
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- refreshImage();
- topleft->update();
- topright->update();
- bottomleft->update();
- bottomright->update();
- }, lifetime());
- }
- void Members::peerListSetTitle(rpl::producer<QString> title) {
- }
- void Members::peerListSetAdditionalTitle(rpl::producer<QString> title) {
- }
- void Members::peerListSetHideEmpty(bool hide) {
- }
- bool Members::peerListIsRowChecked(not_null<PeerListRow*> row) {
- return false;
- }
- void Members::peerListScrollToTop() {
- }
- int Members::peerListSelectedRowsCount() {
- return 0;
- }
- void Members::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
- Unexpected("Item selection in Calls::Members.");
- }
- void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
- Unexpected("Item selection in Calls::Members.");
- }
- void Members::peerListFinishSelectedRowsBunch() {
- }
- void Members::peerListSetDescription(
- object_ptr<Ui::FlatLabel> description) {
- description.destroy();
- }
- std::shared_ptr<Main::SessionShow> Members::peerListUiShow() {
- Unexpected("...Members::peerListUiShow");
- }
- } // namespace Calls::Group
|