calls_group_members.cpp 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/group/calls_group_members.h"
  8. #include "calls/group/calls_cover_item.h"
  9. #include "calls/group/calls_group_call.h"
  10. #include "calls/group/calls_group_menu.h"
  11. #include "calls/group/calls_volume_item.h"
  12. #include "calls/group/calls_group_members_row.h"
  13. #include "calls/group/calls_group_viewport.h"
  14. #include "data/data_channel.h"
  15. #include "data/data_chat.h"
  16. #include "data/data_user.h"
  17. #include "data/data_peer.h"
  18. #include "data/data_changes.h"
  19. #include "data/data_group_call.h"
  20. #include "data/data_peer_values.h" // Data::CanWriteValue.
  21. #include "data/data_session.h" // Data::Session::invitedToCallUsers.
  22. #include "settings/settings_common.h"
  23. #include "ui/widgets/buttons.h"
  24. #include "ui/widgets/scroll_area.h"
  25. #include "ui/widgets/popup_menu.h"
  26. #include "ui/effects/ripple_animation.h"
  27. #include "ui/effects/cross_line.h"
  28. #include "ui/painter.h"
  29. #include "ui/power_saving.h"
  30. #include "core/application.h" // Core::App().domain, .activeWindow.
  31. #include "main/main_domain.h" // Core::App().domain().activate.
  32. #include "main/main_session.h"
  33. #include "lang/lang_keys.h"
  34. #include "info/profile/info_profile_values.h" // Info::Profile::NameValue.
  35. #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
  36. #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfo...
  37. #include "window/window_controller.h" // Controller::sessionController.
  38. #include "window/window_session_controller.h"
  39. #include "webrtc/webrtc_video_track.h"
  40. #include "styles/style_calls.h"
  41. namespace Calls::Group {
  42. namespace {
  43. constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
  44. using Row = MembersRow;
  45. } // namespace
  46. class Members::Controller final
  47. : public PeerListController
  48. , public MembersRowDelegate
  49. , public base::has_weak_ptr {
  50. public:
  51. Controller(
  52. not_null<GroupCall*> call,
  53. not_null<QWidget*> menuParent,
  54. PanelMode mode);
  55. ~Controller();
  56. using MuteRequest = Group::MuteRequest;
  57. using VolumeRequest = Group::VolumeRequest;
  58. Main::Session &session() const override;
  59. void prepare() override;
  60. void rowClicked(not_null<PeerListRow*> row) override;
  61. void rowRightActionClicked(not_null<PeerListRow*> row) override;
  62. base::unique_qptr<Ui::PopupMenu> rowContextMenu(
  63. QWidget *parent,
  64. not_null<PeerListRow*> row) override;
  65. void loadMoreRows() override;
  66. [[nodiscard]] rpl::producer<int> fullCountValue() const {
  67. return _fullCount.value();
  68. }
  69. [[nodiscard]] rpl::producer<MuteRequest> toggleMuteRequests() const;
  70. [[nodiscard]] rpl::producer<VolumeRequest> changeVolumeRequests() const;
  71. [[nodiscard]] auto kickParticipantRequests() const
  72. -> rpl::producer<not_null<PeerData*>>;
  73. Row *findRow(not_null<PeerData*> participantPeer) const;
  74. void setMode(PanelMode mode);
  75. bool rowIsMe(not_null<PeerData*> participantPeer) override;
  76. bool rowCanMuteMembers() override;
  77. void rowUpdateRow(not_null<Row*> row) override;
  78. void rowScheduleRaisedHandStatusRemove(not_null<Row*> row) override;
  79. void rowPaintIcon(
  80. QPainter &p,
  81. QRect rect,
  82. const IconState &state) override;
  83. int rowPaintStatusIcon(
  84. QPainter &p,
  85. int x,
  86. int y,
  87. int outerWidth,
  88. not_null<MembersRow*> row,
  89. const IconState &state) override;
  90. bool rowIsNarrow() override;
  91. void rowShowContextMenu(not_null<PeerListRow*> row) override;
  92. private:
  93. [[nodiscard]] std::unique_ptr<Row> createRowForMe();
  94. [[nodiscard]] std::unique_ptr<Row> createRow(
  95. const Data::GroupCallParticipant &participant);
  96. [[nodiscard]] std::unique_ptr<Row> createInvitedRow(
  97. not_null<PeerData*> participantPeer);
  98. [[nodiscard]] bool isMe(not_null<PeerData*> participantPeer) const;
  99. void prepareRows(not_null<Data::GroupCall*> real);
  100. [[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
  101. QWidget *parent,
  102. not_null<PeerListRow*> row);
  103. void addMuteActionsToContextMenu(
  104. not_null<Ui::PopupMenu*> menu,
  105. not_null<PeerData*> participantPeer,
  106. bool participantIsCallAdmin,
  107. not_null<Row*> row);
  108. void setupListChangeViewers();
  109. void subscribeToChanges(not_null<Data::GroupCall*> real);
  110. void updateRow(
  111. const std::optional<Data::GroupCallParticipant> &was,
  112. const Data::GroupCallParticipant &now);
  113. void updateRow(
  114. not_null<Row*> row,
  115. const std::optional<Data::GroupCallParticipant> &was,
  116. const Data::GroupCallParticipant *participant);
  117. void updateRowInSoundingMap(
  118. not_null<Row*> row,
  119. bool wasSounding,
  120. uint32 wasSsrc,
  121. uint32 wasAdditionalSsrc,
  122. const Data::GroupCallParticipant *participant);
  123. void updateRowInSoundingMap(
  124. not_null<Row*> row,
  125. bool wasSounding,
  126. uint32 wasSsrc,
  127. bool nowSounding,
  128. uint32 nowSsrc);
  129. void removeRow(not_null<Row*> row);
  130. void removeRowFromSoundingMap(not_null<Row*> row);
  131. void updateRowLevel(not_null<Row*> row, float level);
  132. void checkRowPosition(not_null<Row*> row);
  133. [[nodiscard]] bool needToReorder(not_null<Row*> row) const;
  134. [[nodiscard]] bool allRowsAboveAreSpeaking(not_null<Row*> row) const;
  135. [[nodiscard]] bool allRowsAboveMoreImportantThanHand(
  136. not_null<Row*> row,
  137. uint64 raiseHandRating) const;
  138. [[nodiscard]] const Data::GroupCallParticipant *findParticipant(
  139. const std::string &endpoint) const;
  140. [[nodiscard]] const std::string &computeScreenEndpoint(
  141. not_null<const Data::GroupCallParticipant*> participant) const;
  142. [[nodiscard]] const std::string &computeCameraEndpoint(
  143. not_null<const Data::GroupCallParticipant*> participant) const;
  144. void showRowMenu(not_null<PeerListRow*> row, bool highlightRow);
  145. void toggleVideoEndpointActive(
  146. const VideoEndpoint &endpoint,
  147. bool active);
  148. void appendInvitedUsers();
  149. void scheduleRaisedHandStatusRemove();
  150. void hideRowsWithVideoExcept(const VideoEndpoint &large);
  151. void showAllHiddenRows();
  152. void hideRowWithVideo(const VideoEndpoint &endpoint);
  153. void showRowWithVideo(const VideoEndpoint &endpoint);
  154. const not_null<GroupCall*> _call;
  155. not_null<PeerData*> _peer;
  156. std::string _largeEndpoint;
  157. bool _prepared = false;
  158. rpl::event_stream<MuteRequest> _toggleMuteRequests;
  159. rpl::event_stream<VolumeRequest> _changeVolumeRequests;
  160. rpl::event_stream<not_null<PeerData*>> _kickParticipantRequests;
  161. rpl::variable<int> _fullCount = 1;
  162. not_null<QWidget*> _menuParent;
  163. base::unique_qptr<Ui::PopupMenu> _menu;
  164. base::flat_set<not_null<PeerData*>> _menuCheckRowsAfterHidden;
  165. base::flat_map<PeerListRowId, crl::time> _raisedHandStatusRemoveAt;
  166. base::Timer _raisedHandStatusRemoveTimer;
  167. base::flat_map<uint32, not_null<Row*>> _soundingRowBySsrc;
  168. base::flat_set<not_null<PeerData*>> _cameraActive;
  169. base::flat_set<not_null<PeerData*>> _screenActive;
  170. Ui::Animations::Basic _soundingAnimation;
  171. crl::time _soundingAnimationHideLastTime = 0;
  172. bool _skipRowLevelUpdate = false;
  173. PanelMode _mode = PanelMode::Default;
  174. Ui::CrossLineAnimation _inactiveCrossLine;
  175. Ui::CrossLineAnimation _coloredCrossLine;
  176. Ui::CrossLineAnimation _inactiveNarrowCrossLine;
  177. Ui::CrossLineAnimation _coloredNarrowCrossLine;
  178. Ui::CrossLineAnimation _videoCrossLine;
  179. Ui::RoundRect _narrowRoundRectSelected;
  180. Ui::RoundRect _narrowRoundRect;
  181. QImage _narrowShadow;
  182. rpl::lifetime _lifetime;
  183. };
  184. Members::Controller::Controller(
  185. not_null<GroupCall*> call,
  186. not_null<QWidget*> menuParent,
  187. PanelMode mode)
  188. : _call(call)
  189. , _peer(call->peer())
  190. , _menuParent(menuParent)
  191. , _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); })
  192. , _mode(mode)
  193. , _inactiveCrossLine(st::groupCallMemberInactiveCrossLine)
  194. , _coloredCrossLine(st::groupCallMemberColoredCrossLine)
  195. , _inactiveNarrowCrossLine(st::groupCallNarrowInactiveCrossLine)
  196. , _coloredNarrowCrossLine(st::groupCallNarrowColoredCrossLine)
  197. , _videoCrossLine(st::groupCallVideoCrossLine)
  198. , _narrowRoundRectSelected(
  199. ImageRoundRadius::Large,
  200. st::groupCallMembersBgOver)
  201. , _narrowRoundRect(ImageRoundRadius::Large, st::groupCallMembersBg) {
  202. style::PaletteChanged(
  203. ) | rpl::start_with_next([=] {
  204. _inactiveCrossLine.invalidate();
  205. _coloredCrossLine.invalidate();
  206. _inactiveNarrowCrossLine.invalidate();
  207. _coloredNarrowCrossLine.invalidate();
  208. }, _lifetime);
  209. rpl::combine(
  210. PowerSaving::OnValue(PowerSaving::kCalls),
  211. Core::App().appDeactivatedValue()
  212. ) | rpl::start_with_next([=](bool disabled, bool deactivated) {
  213. const auto hide = disabled || deactivated;
  214. if (!(hide && _soundingAnimationHideLastTime)) {
  215. _soundingAnimationHideLastTime = hide ? crl::now() : 0;
  216. }
  217. for (const auto &[_, row] : _soundingRowBySsrc) {
  218. if (hide) {
  219. updateRowLevel(row, 0.);
  220. }
  221. row->setSkipLevelUpdate(hide);
  222. }
  223. if (!hide && !_soundingAnimation.animating()) {
  224. _soundingAnimation.start();
  225. }
  226. _skipRowLevelUpdate = hide;
  227. }, _lifetime);
  228. _soundingAnimation.init([=](crl::time now) {
  229. if (const auto &last = _soundingAnimationHideLastTime; (last > 0)
  230. && (now - last >= kBlobsEnterDuration)) {
  231. _soundingAnimation.stop();
  232. return false;
  233. }
  234. for (const auto &[ssrc, row] : _soundingRowBySsrc) {
  235. row->updateBlobAnimation(now);
  236. delegate()->peerListUpdateRow(row);
  237. }
  238. return true;
  239. });
  240. _peer->session().changes().peerUpdates(
  241. Data::PeerUpdate::Flag::About
  242. ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
  243. if (const auto row = findRow(update.peer)) {
  244. row->setAbout(update.peer->about());
  245. }
  246. }, _lifetime);
  247. }
  248. Members::Controller::~Controller() {
  249. base::take(_menu);
  250. }
  251. void Members::Controller::setupListChangeViewers() {
  252. _call->real(
  253. ) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
  254. subscribeToChanges(real);
  255. }, _lifetime);
  256. _call->levelUpdates(
  257. ) | rpl::start_with_next([=](const LevelUpdate &update) {
  258. const auto i = _soundingRowBySsrc.find(update.ssrc);
  259. if (i != end(_soundingRowBySsrc)) {
  260. updateRowLevel(i->second, update.value);
  261. }
  262. }, _lifetime);
  263. _call->videoEndpointLargeValue(
  264. ) | rpl::start_with_next([=](const VideoEndpoint &large) {
  265. if (large) {
  266. hideRowsWithVideoExcept(large);
  267. } else {
  268. showAllHiddenRows();
  269. }
  270. }, _lifetime);
  271. _call->videoStreamShownUpdates(
  272. ) | rpl::filter([=](const VideoStateToggle &update) {
  273. const auto &large = _call->videoEndpointLarge();
  274. return large && (update.endpoint != large);
  275. }) | rpl::start_with_next([=](const VideoStateToggle &update) {
  276. if (update.value) {
  277. hideRowWithVideo(update.endpoint);
  278. } else {
  279. showRowWithVideo(update.endpoint);
  280. }
  281. }, _lifetime);
  282. _call->rejoinEvents(
  283. ) | rpl::start_with_next([=](const Group::RejoinEvent &event) {
  284. const auto guard = gsl::finally([&] {
  285. delegate()->peerListRefreshRows();
  286. });
  287. if (const auto row = findRow(event.wasJoinAs)) {
  288. removeRow(row);
  289. }
  290. if (findRow(event.nowJoinAs)) {
  291. return;
  292. } else if (auto row = createRowForMe()) {
  293. delegate()->peerListAppendRow(std::move(row));
  294. }
  295. }, _lifetime);
  296. }
  297. void Members::Controller::hideRowsWithVideoExcept(
  298. const VideoEndpoint &large) {
  299. auto changed = false;
  300. auto showLargeRow = true;
  301. for (const auto &endpoint : _call->shownVideoTracks()) {
  302. if (endpoint != large) {
  303. if (const auto row = findRow(endpoint.peer)) {
  304. if (endpoint.peer == large.peer) {
  305. showLargeRow = false;
  306. }
  307. delegate()->peerListSetRowHidden(row, true);
  308. changed = true;
  309. }
  310. }
  311. }
  312. if (const auto row = showLargeRow ? findRow(large.peer) : nullptr) {
  313. delegate()->peerListSetRowHidden(row, false);
  314. changed = true;
  315. }
  316. if (changed) {
  317. delegate()->peerListRefreshRows();
  318. }
  319. }
  320. void Members::Controller::showAllHiddenRows() {
  321. auto shown = false;
  322. for (const auto &endpoint : _call->shownVideoTracks()) {
  323. if (const auto row = findRow(endpoint.peer)) {
  324. delegate()->peerListSetRowHidden(row, false);
  325. shown = true;
  326. }
  327. }
  328. if (shown) {
  329. delegate()->peerListRefreshRows();
  330. }
  331. }
  332. void Members::Controller::hideRowWithVideo(const VideoEndpoint &endpoint) {
  333. if (const auto row = findRow(endpoint.peer)) {
  334. delegate()->peerListSetRowHidden(row, true);
  335. delegate()->peerListRefreshRows();
  336. }
  337. }
  338. void Members::Controller::showRowWithVideo(const VideoEndpoint &endpoint) {
  339. const auto peer = endpoint.peer;
  340. const auto &large = _call->videoEndpointLarge();
  341. if (large) {
  342. for (const auto &endpoint : _call->shownVideoTracks()) {
  343. if (endpoint != large && endpoint.peer == peer) {
  344. // Still hidden with another video.
  345. return;
  346. }
  347. }
  348. }
  349. if (const auto row = findRow(endpoint.peer)) {
  350. delegate()->peerListSetRowHidden(row, false);
  351. delegate()->peerListRefreshRows();
  352. }
  353. }
  354. void Members::Controller::subscribeToChanges(not_null<Data::GroupCall*> real) {
  355. _fullCount = real->fullCountValue();
  356. real->participantsReloaded(
  357. ) | rpl::start_with_next([=] {
  358. prepareRows(real);
  359. }, _lifetime);
  360. using Update = Data::GroupCall::ParticipantUpdate;
  361. real->participantUpdated(
  362. ) | rpl::start_with_next([=](const Update &update) {
  363. Expects(update.was.has_value() || update.now.has_value());
  364. const auto participantPeer = update.was
  365. ? update.was->peer
  366. : update.now->peer;
  367. if (!update.now) {
  368. if (const auto row = findRow(participantPeer)) {
  369. if (isMe(participantPeer)) {
  370. updateRow(row, update.was, nullptr);
  371. } else {
  372. removeRow(row);
  373. delegate()->peerListRefreshRows();
  374. }
  375. }
  376. } else {
  377. updateRow(update.was, *update.now);
  378. }
  379. }, _lifetime);
  380. for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
  381. toggleVideoEndpointActive(endpoint, true);
  382. }
  383. _call->videoStreamActiveUpdates(
  384. ) | rpl::start_with_next([=](const VideoStateToggle &update) {
  385. toggleVideoEndpointActive(update.endpoint, update.value);
  386. }, _lifetime);
  387. if (_prepared) {
  388. appendInvitedUsers();
  389. }
  390. }
  391. void Members::Controller::toggleVideoEndpointActive(
  392. const VideoEndpoint &endpoint,
  393. bool active) {
  394. const auto toggleOne = [=](
  395. base::flat_set<not_null<PeerData*>> &set,
  396. not_null<PeerData*> participantPeer,
  397. bool active) {
  398. if ((active && set.emplace(participantPeer).second)
  399. || (!active && set.remove(participantPeer))) {
  400. if (_mode == PanelMode::Wide) {
  401. if (const auto row = findRow(participantPeer)) {
  402. delegate()->peerListUpdateRow(row);
  403. }
  404. }
  405. }
  406. };
  407. const auto &id = endpoint.id;
  408. const auto participantPeer = endpoint.peer;
  409. const auto real = _call->lookupReal();
  410. if (active) {
  411. if (const auto participant = findParticipant(id)) {
  412. if (computeCameraEndpoint(participant) == id) {
  413. toggleOne(_cameraActive, participantPeer, true);
  414. } else if (computeScreenEndpoint(participant) == id) {
  415. toggleOne(_screenActive, participantPeer, true);
  416. }
  417. }
  418. } else if (const auto participant = real->participantByPeer(
  419. participantPeer)) {
  420. const auto &camera = computeCameraEndpoint(participant);
  421. const auto &screen = computeScreenEndpoint(participant);
  422. if (camera == id || camera.empty()) {
  423. toggleOne(_cameraActive, participantPeer, false);
  424. }
  425. if (screen == id || screen.empty()) {
  426. toggleOne(_screenActive, participantPeer, false);
  427. }
  428. } else {
  429. toggleOne(_cameraActive, participantPeer, false);
  430. toggleOne(_screenActive, participantPeer, false);
  431. }
  432. }
  433. void Members::Controller::appendInvitedUsers() {
  434. if (const auto id = _call->id()) {
  435. for (const auto &user : _peer->owner().invitedToCallUsers(id)) {
  436. if (auto row = createInvitedRow(user)) {
  437. delegate()->peerListAppendRow(std::move(row));
  438. }
  439. }
  440. delegate()->peerListRefreshRows();
  441. }
  442. using Invite = Data::Session::InviteToCall;
  443. _peer->owner().invitesToCalls(
  444. ) | rpl::filter([=](const Invite &invite) {
  445. return (invite.id == _call->id());
  446. }) | rpl::start_with_next([=](const Invite &invite) {
  447. if (auto row = createInvitedRow(invite.user)) {
  448. delegate()->peerListAppendRow(std::move(row));
  449. delegate()->peerListRefreshRows();
  450. }
  451. }, _lifetime);
  452. }
  453. void Members::Controller::updateRow(
  454. const std::optional<Data::GroupCallParticipant> &was,
  455. const Data::GroupCallParticipant &now) {
  456. auto reorderIfInvitedBefore = 0;
  457. auto checkPosition = (Row*)nullptr;
  458. auto addedToBottom = (Row*)nullptr;
  459. if (const auto row = findRow(now.peer)) {
  460. if (row->state() == Row::State::Invited) {
  461. reorderIfInvitedBefore = row->absoluteIndex();
  462. }
  463. updateRow(row, was, &now);
  464. if ((now.speaking && (!was || !was->speaking))
  465. || (now.raisedHandRating != (was ? was->raisedHandRating : 0))
  466. || (!now.canSelfUnmute && was && was->canSelfUnmute)) {
  467. checkPosition = row;
  468. }
  469. } else if (auto row = createRow(now)) {
  470. if (row->speaking()) {
  471. delegate()->peerListPrependRow(std::move(row));
  472. } else {
  473. reorderIfInvitedBefore = delegate()->peerListFullRowsCount();
  474. if (now.raisedHandRating != 0) {
  475. checkPosition = row.get();
  476. } else {
  477. addedToBottom = row.get();
  478. }
  479. delegate()->peerListAppendRow(std::move(row));
  480. }
  481. delegate()->peerListRefreshRows();
  482. }
  483. static constexpr auto kInvited = Row::State::Invited;
  484. const auto reorder = [&] {
  485. const auto count = reorderIfInvitedBefore;
  486. if (count <= 0) {
  487. return false;
  488. }
  489. const auto row = delegate()->peerListRowAt(
  490. reorderIfInvitedBefore - 1).get();
  491. return (static_cast<Row*>(row)->state() == kInvited);
  492. }();
  493. if (reorder) {
  494. delegate()->peerListPartitionRows([](const PeerListRow &row) {
  495. return static_cast<const Row&>(row).state() != kInvited;
  496. });
  497. }
  498. if (checkPosition) {
  499. checkRowPosition(checkPosition);
  500. } else if (addedToBottom) {
  501. const auto real = _call->lookupReal();
  502. if (real && real->joinedToTop()) {
  503. const auto proj = [&](const PeerListRow &other) {
  504. const auto &real = static_cast<const Row&>(other);
  505. return real.speaking()
  506. ? 2
  507. : (&real == addedToBottom)
  508. ? 1
  509. : 0;
  510. };
  511. delegate()->peerListSortRows([&](
  512. const PeerListRow &a,
  513. const PeerListRow &b) {
  514. return proj(a) > proj(b);
  515. });
  516. }
  517. }
  518. }
  519. bool Members::Controller::allRowsAboveAreSpeaking(not_null<Row*> row) const {
  520. const auto count = delegate()->peerListFullRowsCount();
  521. for (auto i = 0; i != count; ++i) {
  522. const auto above = delegate()->peerListRowAt(i);
  523. if (above == row) {
  524. // All rows above are speaking.
  525. return true;
  526. } else if (!static_cast<Row*>(above.get())->speaking()) {
  527. break;
  528. }
  529. }
  530. return false;
  531. }
  532. bool Members::Controller::allRowsAboveMoreImportantThanHand(
  533. not_null<Row*> row,
  534. uint64 raiseHandRating) const {
  535. Expects(raiseHandRating > 0);
  536. const auto count = delegate()->peerListFullRowsCount();
  537. for (auto i = 0; i != count; ++i) {
  538. const auto above = delegate()->peerListRowAt(i);
  539. if (above == row) {
  540. // All rows above are 'more important' than this raised hand.
  541. return true;
  542. }
  543. const auto real = static_cast<Row*>(above.get());
  544. const auto state = real->state();
  545. if (state == Row::State::Muted
  546. || (state == Row::State::RaisedHand
  547. && real->raisedHandRating() < raiseHandRating)) {
  548. break;
  549. }
  550. }
  551. return false;
  552. }
  553. bool Members::Controller::needToReorder(not_null<Row*> row) const {
  554. // All reorder cases:
  555. // - bring speaking up
  556. // - bring raised hand up
  557. // - bring muted down
  558. if (row->speaking()) {
  559. return !allRowsAboveAreSpeaking(row);
  560. } else if (!_peer->canManageGroupCall()) {
  561. // Raising hands reorder participants only for voice chat admins.
  562. return false;
  563. }
  564. const auto rating = row->raisedHandRating();
  565. if (!rating && row->state() != Row::State::Muted) {
  566. return false;
  567. }
  568. if (rating > 0 && !allRowsAboveMoreImportantThanHand(row, rating)) {
  569. return true;
  570. }
  571. const auto index = row->absoluteIndex();
  572. if (index + 1 == delegate()->peerListFullRowsCount()) {
  573. // Last one, can't bring lower.
  574. return false;
  575. }
  576. const auto next = delegate()->peerListRowAt(index + 1);
  577. const auto state = static_cast<Row*>(next.get())->state();
  578. if ((state != Row::State::Muted) && (state != Row::State::RaisedHand)) {
  579. return true;
  580. }
  581. if (!rating && static_cast<Row*>(next.get())->raisedHandRating()) {
  582. return true;
  583. }
  584. return false;
  585. }
  586. void Members::Controller::checkRowPosition(not_null<Row*> row) {
  587. if (_menu) {
  588. // Don't reorder rows while we show the popup menu.
  589. _menuCheckRowsAfterHidden.emplace(row->peer());
  590. return;
  591. } else if (!needToReorder(row)) {
  592. return;
  593. }
  594. // Someone started speaking and has a non-speaking row above him.
  595. // Or someone raised hand and has force muted above him.
  596. // Or someone was forced muted and had can_unmute_self below him. Sort.
  597. static constexpr auto kTop = std::numeric_limits<uint64>::max();
  598. const auto projForAdmin = [&](const PeerListRow &other) {
  599. const auto &real = static_cast<const Row&>(other);
  600. return real.speaking()
  601. // Speaking 'row' to the top, all other speaking below it.
  602. ? (&real == row.get() ? kTop : (kTop - 1))
  603. : (real.raisedHandRating() > 0)
  604. // Then all raised hands sorted by rating.
  605. ? real.raisedHandRating()
  606. : (real.state() == Row::State::Muted)
  607. // All force muted at the bottom, but 'row' still above others.
  608. ? (&real == row.get() ? 1ULL : 0ULL)
  609. // All not force-muted lie between raised hands and speaking.
  610. : (kTop - 2);
  611. };
  612. const auto projForOther = [&](const PeerListRow &other) {
  613. const auto &real = static_cast<const Row&>(other);
  614. return real.speaking()
  615. // Speaking 'row' to the top, all other speaking below it.
  616. ? (&real == row.get() ? kTop : (kTop - 1))
  617. : 0ULL;
  618. };
  619. using Comparator = Fn<bool(const PeerListRow&, const PeerListRow&)>;
  620. const auto makeComparator = [&](const auto &proj) -> Comparator {
  621. return [&](const PeerListRow &a, const PeerListRow &b) {
  622. return proj(a) > proj(b);
  623. };
  624. };
  625. delegate()->peerListSortRows(_peer->canManageGroupCall()
  626. ? makeComparator(projForAdmin)
  627. : makeComparator(projForOther));
  628. }
  629. void Members::Controller::updateRow(
  630. not_null<Row*> row,
  631. const std::optional<Data::GroupCallParticipant> &was,
  632. const Data::GroupCallParticipant *participant) {
  633. const auto wasSounding = row->sounding();
  634. const auto wasSsrc = was ? was->ssrc : 0;
  635. const auto wasAdditionalSsrc = was
  636. ? GetAdditionalAudioSsrc(was->videoParams)
  637. : 0;
  638. row->setSkipLevelUpdate(_skipRowLevelUpdate);
  639. row->updateState(participant);
  640. const auto wasNoSounding = _soundingRowBySsrc.empty();
  641. updateRowInSoundingMap(
  642. row,
  643. wasSounding,
  644. wasSsrc,
  645. wasAdditionalSsrc,
  646. participant);
  647. const auto nowNoSounding = _soundingRowBySsrc.empty();
  648. if (wasNoSounding && !nowNoSounding) {
  649. _soundingAnimation.start();
  650. } else if (nowNoSounding && !wasNoSounding) {
  651. _soundingAnimation.stop();
  652. }
  653. delegate()->peerListUpdateRow(row);
  654. }
  655. void Members::Controller::updateRowInSoundingMap(
  656. not_null<Row*> row,
  657. bool wasSounding,
  658. uint32 wasSsrc,
  659. uint32 wasAdditionalSsrc,
  660. const Data::GroupCallParticipant *participant) {
  661. const auto nowSounding = row->sounding();
  662. const auto nowSsrc = participant ? participant->ssrc : 0;
  663. const auto nowAdditionalSsrc = participant
  664. ? GetAdditionalAudioSsrc(participant->videoParams)
  665. : 0;
  666. updateRowInSoundingMap(row, wasSounding, wasSsrc, nowSounding, nowSsrc);
  667. updateRowInSoundingMap(
  668. row,
  669. wasSounding,
  670. wasAdditionalSsrc,
  671. nowSounding,
  672. nowAdditionalSsrc);
  673. }
  674. void Members::Controller::updateRowInSoundingMap(
  675. not_null<Row*> row,
  676. bool wasSounding,
  677. uint32 wasSsrc,
  678. bool nowSounding,
  679. uint32 nowSsrc) {
  680. if (wasSsrc == nowSsrc) {
  681. if (nowSsrc && nowSounding != wasSounding) {
  682. if (nowSounding) {
  683. _soundingRowBySsrc.emplace(nowSsrc, row);
  684. } else {
  685. _soundingRowBySsrc.remove(nowSsrc);
  686. }
  687. }
  688. } else {
  689. _soundingRowBySsrc.remove(wasSsrc);
  690. if (nowSounding && nowSsrc) {
  691. _soundingRowBySsrc.emplace(nowSsrc, row);
  692. }
  693. }
  694. }
  695. void Members::Controller::removeRow(not_null<Row*> row) {
  696. removeRowFromSoundingMap(row);
  697. delegate()->peerListRemoveRow(row);
  698. }
  699. void Members::Controller::removeRowFromSoundingMap(not_null<Row*> row) {
  700. // There may be 0, 1 or 2 entries for a row.
  701. for (auto i = begin(_soundingRowBySsrc); i != end(_soundingRowBySsrc);) {
  702. if (i->second == row) {
  703. i = _soundingRowBySsrc.erase(i);
  704. } else {
  705. ++i;
  706. }
  707. }
  708. }
  709. void Members::Controller::updateRowLevel(
  710. not_null<Row*> row,
  711. float level) {
  712. if (_skipRowLevelUpdate) {
  713. return;
  714. }
  715. row->updateLevel(level);
  716. }
  717. Row *Members::Controller::findRow(
  718. not_null<PeerData*> participantPeer) const {
  719. return static_cast<Row*>(
  720. delegate()->peerListFindRow(participantPeer->id.value));
  721. }
  722. void Members::Controller::setMode(PanelMode mode) {
  723. _mode = mode;
  724. }
  725. const Data::GroupCallParticipant *Members::Controller::findParticipant(
  726. const std::string &endpoint) const {
  727. if (endpoint.empty()) {
  728. return nullptr;
  729. }
  730. const auto real = _call->lookupReal();
  731. if (!real) {
  732. return nullptr;
  733. } else if (endpoint == _call->screenSharingEndpoint()
  734. || endpoint == _call->cameraSharingEndpoint()) {
  735. return real->participantByPeer(_call->joinAs());
  736. } else {
  737. return real->participantByEndpoint(endpoint);
  738. }
  739. }
  740. const std::string &Members::Controller::computeScreenEndpoint(
  741. not_null<const Data::GroupCallParticipant*> participant) const {
  742. return (participant->peer == _call->joinAs())
  743. ? _call->screenSharingEndpoint()
  744. : participant->screenEndpoint();
  745. }
  746. const std::string &Members::Controller::computeCameraEndpoint(
  747. not_null<const Data::GroupCallParticipant*> participant) const {
  748. return (participant->peer == _call->joinAs())
  749. ? _call->cameraSharingEndpoint()
  750. : participant->cameraEndpoint();
  751. }
  752. Main::Session &Members::Controller::session() const {
  753. return _call->peer()->session();
  754. }
  755. void Members::Controller::prepare() {
  756. delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled);
  757. setDescription(nullptr);
  758. setSearchNoResults(nullptr);
  759. if (const auto real = _call->lookupReal()) {
  760. prepareRows(real);
  761. } else if (auto row = createRowForMe()) {
  762. delegate()->peerListAppendRow(std::move(row));
  763. delegate()->peerListRefreshRows();
  764. }
  765. loadMoreRows();
  766. appendInvitedUsers();
  767. _prepared = true;
  768. setupListChangeViewers();
  769. }
  770. bool Members::Controller::isMe(not_null<PeerData*> participantPeer) const {
  771. return (_call->joinAs() == participantPeer);
  772. }
  773. void Members::Controller::prepareRows(not_null<Data::GroupCall*> real) {
  774. auto foundMe = false;
  775. auto changed = false;
  776. auto count = delegate()->peerListFullRowsCount();
  777. for (auto i = 0; i != count;) {
  778. const auto row = static_cast<Row*>(
  779. delegate()->peerListRowAt(i).get());
  780. removeRowFromSoundingMap(row);
  781. const auto participantPeer = row->peer();
  782. const auto me = isMe(participantPeer);
  783. if (me) {
  784. foundMe = true;
  785. }
  786. if (const auto found = real->participantByPeer(participantPeer)) {
  787. updateRowInSoundingMap(row, false, 0, 0, found);
  788. ++i;
  789. } else if (me) {
  790. ++i;
  791. } else {
  792. changed = true;
  793. removeRow(row);
  794. --count;
  795. }
  796. }
  797. if (!foundMe) {
  798. const auto me = _call->joinAs();
  799. const auto participant = real->participantByPeer(me);
  800. auto row = participant
  801. ? createRow(*participant)
  802. : createRowForMe();
  803. if (row) {
  804. changed = true;
  805. delegate()->peerListAppendRow(std::move(row));
  806. }
  807. }
  808. for (const auto &participant : real->participants()) {
  809. if (auto row = createRow(participant)) {
  810. changed = true;
  811. delegate()->peerListAppendRow(std::move(row));
  812. }
  813. }
  814. if (changed) {
  815. delegate()->peerListRefreshRows();
  816. }
  817. }
  818. void Members::Controller::loadMoreRows() {
  819. if (const auto real = _call->lookupReal()) {
  820. real->requestParticipants();
  821. }
  822. }
  823. auto Members::Controller::toggleMuteRequests() const
  824. -> rpl::producer<MuteRequest> {
  825. return _toggleMuteRequests.events();
  826. }
  827. auto Members::Controller::changeVolumeRequests() const
  828. -> rpl::producer<VolumeRequest> {
  829. return _changeVolumeRequests.events();
  830. }
  831. bool Members::Controller::rowIsMe(not_null<PeerData*> participantPeer) {
  832. return isMe(participantPeer);
  833. }
  834. bool Members::Controller::rowCanMuteMembers() {
  835. return _peer->canManageGroupCall();
  836. }
  837. void Members::Controller::rowUpdateRow(not_null<Row*> row) {
  838. delegate()->peerListUpdateRow(row);
  839. }
  840. void Members::Controller::rowScheduleRaisedHandStatusRemove(
  841. not_null<Row*> row) {
  842. const auto id = row->id();
  843. const auto when = crl::now() + kKeepRaisedHandStatusDuration;
  844. const auto i = _raisedHandStatusRemoveAt.find(id);
  845. if (i != _raisedHandStatusRemoveAt.end()) {
  846. i->second = when;
  847. } else {
  848. _raisedHandStatusRemoveAt.emplace(id, when);
  849. }
  850. scheduleRaisedHandStatusRemove();
  851. }
  852. void Members::Controller::scheduleRaisedHandStatusRemove() {
  853. auto waiting = crl::time(0);
  854. const auto now = crl::now();
  855. for (auto i = begin(_raisedHandStatusRemoveAt)
  856. ; i != end(_raisedHandStatusRemoveAt);) {
  857. if (i->second <= now) {
  858. if (const auto row = delegate()->peerListFindRow(i->first)) {
  859. static_cast<Row*>(row)->clearRaisedHandStatus();
  860. }
  861. i = _raisedHandStatusRemoveAt.erase(i);
  862. } else {
  863. if (!waiting || waiting > (i->second - now)) {
  864. waiting = i->second - now;
  865. }
  866. ++i;
  867. }
  868. }
  869. if (waiting > 0) {
  870. if (!_raisedHandStatusRemoveTimer.isActive()
  871. || _raisedHandStatusRemoveTimer.remainingTime() > waiting) {
  872. _raisedHandStatusRemoveTimer.callOnce(waiting);
  873. }
  874. }
  875. }
  876. void Members::Controller::rowPaintIcon(
  877. QPainter &p,
  878. QRect rect,
  879. const IconState &state) {
  880. if (_mode == PanelMode::Wide
  881. && state.style == MembersRowStyle::Default) {
  882. return;
  883. }
  884. const auto narrow = (state.style == MembersRowStyle::Narrow);
  885. if (state.invited) {
  886. if (narrow) {
  887. st::groupCallNarrowInvitedIcon.paintInCenter(p, rect);
  888. } else {
  889. st::groupCallMemberInvited.paintInCenter(
  890. p,
  891. QRect(
  892. rect.topLeft() + st::groupCallMemberInvitedPosition,
  893. st::groupCallMemberInvited.size()));
  894. }
  895. return;
  896. }
  897. const auto video = (state.style == MembersRowStyle::Video);
  898. const auto &greenIcon = video
  899. ? st::groupCallVideoCrossLine.icon
  900. : narrow
  901. ? st::groupCallNarrowColoredCrossLine.icon
  902. : st::groupCallMemberColoredCrossLine.icon;
  903. const auto left = rect.x() + (rect.width() - greenIcon.width()) / 2;
  904. const auto top = rect.y() + (rect.height() - greenIcon.height()) / 2;
  905. if (state.speaking == 1. && !state.mutedByMe) {
  906. // Just green icon, no cross, no coloring.
  907. greenIcon.paintInCenter(p, rect);
  908. return;
  909. } else if (state.speaking == 0. && (!narrow || !state.mutedByMe)) {
  910. if (state.active == 1.) {
  911. // Just gray icon, no cross, no coloring.
  912. const auto &grayIcon = video
  913. ? st::groupCallVideoCrossLine.icon
  914. : narrow
  915. ? st::groupCallNarrowInactiveCrossLine.icon
  916. : st::groupCallMemberInactiveCrossLine.icon;
  917. grayIcon.paintInCenter(p, rect);
  918. return;
  919. } else if (state.active == 0.) {
  920. if (state.muted == 1.) {
  921. if (state.raisedHand) {
  922. (narrow
  923. ? st::groupCallNarrowRaisedHand
  924. : st::groupCallMemberRaisedHand).paintInCenter(p, rect);
  925. return;
  926. }
  927. // Red crossed icon, colorized once, cached as last frame.
  928. auto &line = video
  929. ? _videoCrossLine
  930. : narrow
  931. ? _coloredNarrowCrossLine
  932. : _coloredCrossLine;
  933. const auto color = video
  934. ? std::nullopt
  935. : std::make_optional(st::groupCallMemberMutedIcon->c);
  936. line.paint(
  937. p,
  938. left,
  939. top,
  940. 1.,
  941. color);
  942. return;
  943. } else if (state.muted == 0.) {
  944. // Gray crossed icon, no coloring, cached as last frame.
  945. auto &line = video
  946. ? _videoCrossLine
  947. : narrow
  948. ? _inactiveNarrowCrossLine
  949. : _inactiveCrossLine;
  950. line.paint(p, left, top, 1.);
  951. return;
  952. }
  953. }
  954. }
  955. const auto activeInactiveColor = anim::color(
  956. (narrow
  957. ? st::groupCallMemberNotJoinedStatus
  958. : st::groupCallMemberInactiveIcon),
  959. (narrow
  960. ? st::groupCallMemberActiveStatus
  961. : state.mutedByMe
  962. ? st::groupCallMemberMutedIcon
  963. : st::groupCallMemberActiveIcon),
  964. state.speaking);
  965. const auto iconColor = anim::color(
  966. activeInactiveColor,
  967. st::groupCallMemberMutedIcon,
  968. state.muted);
  969. const auto color = video
  970. ? std::nullopt
  971. : std::make_optional((narrow && state.mutedByMe)
  972. ? st::groupCallMemberMutedIcon->c
  973. : (narrow && state.raisedHand)
  974. ? st::groupCallMemberInactiveStatus->c
  975. : iconColor);
  976. // Don't use caching of the last frame,
  977. // because 'muted' may animate color.
  978. const auto crossProgress = std::min(1. - state.active, 0.9999);
  979. auto &line = video
  980. ? _videoCrossLine
  981. : narrow
  982. ? _inactiveNarrowCrossLine
  983. : _inactiveCrossLine;
  984. line.paint(p, left, top, crossProgress, color);
  985. }
  986. int Members::Controller::rowPaintStatusIcon(
  987. QPainter &p,
  988. int x,
  989. int y,
  990. int outerWidth,
  991. not_null<MembersRow*> row,
  992. const IconState &state) {
  993. Expects(state.style == MembersRowStyle::Narrow);
  994. if (_mode != PanelMode::Wide) {
  995. return 0;
  996. }
  997. const auto &icon = st::groupCallNarrowColoredCrossLine.icon;
  998. x += st::groupCallNarrowIconPosition.x();
  999. y += st::groupCallNarrowIconPosition.y();
  1000. const auto rect = QRect(x, y, icon.width(), icon.height());
  1001. rowPaintIcon(p, rect, state);
  1002. x += icon.width();
  1003. auto result = st::groupCallNarrowIconSkip;
  1004. const auto participantPeer = row->peer();
  1005. const auto camera = _cameraActive.contains(participantPeer);
  1006. const auto screen = _screenActive.contains(participantPeer);
  1007. if (camera || screen) {
  1008. const auto activeInactiveColor = anim::color(
  1009. st::groupCallMemberNotJoinedStatus,
  1010. st::groupCallMemberActiveStatus,
  1011. state.speaking);
  1012. const auto iconColor = anim::color(
  1013. activeInactiveColor,
  1014. st::groupCallMemberNotJoinedStatus,
  1015. state.muted);
  1016. const auto other = state.mutedByMe
  1017. ? st::groupCallMemberMutedIcon->c
  1018. : state.raisedHand
  1019. ? st::groupCallMemberInactiveStatus->c
  1020. : iconColor;
  1021. if (camera) {
  1022. st::groupCallNarrowCameraIcon.paint(p, x, y, outerWidth, other);
  1023. x += st::groupCallNarrowCameraIcon.width();
  1024. result += st::groupCallNarrowCameraIcon.width();
  1025. }
  1026. if (screen) {
  1027. st::groupCallNarrowScreenIcon.paint(p, x, y, outerWidth, other);
  1028. x += st::groupCallNarrowScreenIcon.width();
  1029. result += st::groupCallNarrowScreenIcon.width();
  1030. }
  1031. }
  1032. return result;
  1033. }
  1034. bool Members::Controller::rowIsNarrow() {
  1035. return (_mode == PanelMode::Wide);
  1036. }
  1037. void Members::Controller::rowShowContextMenu(not_null<PeerListRow*> row) {
  1038. showRowMenu(row, false);
  1039. }
  1040. auto Members::Controller::kickParticipantRequests() const
  1041. -> rpl::producer<not_null<PeerData*>>{
  1042. return _kickParticipantRequests.events();
  1043. }
  1044. void Members::Controller::rowClicked(not_null<PeerListRow*> row) {
  1045. showRowMenu(row, true);
  1046. }
  1047. void Members::Controller::showRowMenu(
  1048. not_null<PeerListRow*> row,
  1049. bool highlightRow) {
  1050. const auto cleanup = [=](not_null<Ui::PopupMenu*> menu) {
  1051. if (!_menu || _menu.get() != menu) {
  1052. return;
  1053. }
  1054. auto saved = base::take(_menu);
  1055. for (const auto &peer : base::take(_menuCheckRowsAfterHidden)) {
  1056. if (const auto row = findRow(peer)) {
  1057. checkRowPosition(row);
  1058. }
  1059. }
  1060. _menu = std::move(saved);
  1061. };
  1062. delegate()->peerListShowRowMenu(row, highlightRow, cleanup);
  1063. }
  1064. void Members::Controller::rowRightActionClicked(
  1065. not_null<PeerListRow*> row) {
  1066. showRowMenu(row, true);
  1067. }
  1068. base::unique_qptr<Ui::PopupMenu> Members::Controller::rowContextMenu(
  1069. QWidget *parent,
  1070. not_null<PeerListRow*> row) {
  1071. auto result = createRowContextMenu(parent, row);
  1072. if (result) {
  1073. // First clear _menu value, so that we don't check row positions yet.
  1074. base::take(_menu);
  1075. // Here unique_qptr is used like a shared pointer, where
  1076. // not the last destroyed pointer destroys the object, but the first.
  1077. _menu = base::unique_qptr<Ui::PopupMenu>(result.get());
  1078. }
  1079. return result;
  1080. }
  1081. base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
  1082. QWidget *parent,
  1083. not_null<PeerListRow*> row) {
  1084. const auto participantPeer = row->peer();
  1085. const auto real = static_cast<Row*>(row.get());
  1086. const auto muteState = real->state();
  1087. const auto muted = (muteState == Row::State::Muted)
  1088. || (muteState == Row::State::RaisedHand);
  1089. const auto addCover = !_call->rtmp();
  1090. const auto addVolumeItem = (!muted || isMe(participantPeer));
  1091. const auto admin = IsGroupCallAdmin(_peer, participantPeer);
  1092. const auto session = &_peer->session();
  1093. const auto account = &session->account();
  1094. auto result = base::make_unique_q<Ui::PopupMenu>(
  1095. parent,
  1096. (addCover
  1097. ? st::groupCallPopupMenuWithCover
  1098. : addVolumeItem
  1099. ? st::groupCallPopupMenuWithVolume
  1100. : st::groupCallPopupMenu));
  1101. const auto weakMenu = Ui::MakeWeak(result.get());
  1102. const auto withActiveWindow = [=](auto callback) {
  1103. if (const auto window = Core::App().activePrimaryWindow()) {
  1104. if (const auto menu = weakMenu.data()) {
  1105. menu->discardParentReActivate();
  1106. // We must hide PopupMenu before we activate the MainWindow,
  1107. // otherwise we set focus in field inside MainWindow and then
  1108. // PopupMenu::hide activates back the group call panel :(
  1109. delete weakMenu;
  1110. }
  1111. window->invokeForSessionController(
  1112. account,
  1113. participantPeer,
  1114. [&](not_null<Window::SessionController*> newController) {
  1115. callback(newController);
  1116. newController->widget()->activate();
  1117. });
  1118. }
  1119. };
  1120. const auto showProfile = [=] {
  1121. withActiveWindow([=](not_null<Window::SessionController*> window) {
  1122. window->showPeerInfo(participantPeer);
  1123. });
  1124. };
  1125. const auto showHistory = [=] {
  1126. withActiveWindow([=](not_null<Window::SessionController*> window) {
  1127. window->showPeerHistory(
  1128. participantPeer,
  1129. Window::SectionShow::Way::Forward);
  1130. });
  1131. };
  1132. const auto removeFromVoiceChat = crl::guard(this, [=] {
  1133. _kickParticipantRequests.fire_copy(participantPeer);
  1134. });
  1135. if (addCover) {
  1136. result->addAction(base::make_unique_q<CoverItem>(
  1137. result->menu(),
  1138. st::groupCallPopupCoverMenu,
  1139. st::groupCallMenuCover,
  1140. Info::Profile::NameValue(participantPeer),
  1141. PrepareShortInfoStatus(participantPeer),
  1142. PrepareShortInfoUserpic(
  1143. participantPeer,
  1144. st::groupCallMenuCover)));
  1145. if (const auto about = participantPeer->about(); !about.isEmpty()) {
  1146. result->addAction(base::make_unique_q<AboutItem>(
  1147. result->menu(),
  1148. st::groupCallPopupCoverMenu,
  1149. Info::Profile::AboutWithEntities(participantPeer, about)));
  1150. }
  1151. }
  1152. if (const auto real = _call->lookupReal()) {
  1153. auto oneFound = false;
  1154. auto hasTwoOrMore = false;
  1155. const auto &shown = _call->shownVideoTracks();
  1156. for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
  1157. if (shown.contains(endpoint)) {
  1158. if (oneFound) {
  1159. hasTwoOrMore = true;
  1160. break;
  1161. }
  1162. oneFound = true;
  1163. }
  1164. }
  1165. const auto participant = real->participantByPeer(participantPeer);
  1166. if (participant && hasTwoOrMore) {
  1167. const auto &large = _call->videoEndpointLarge();
  1168. const auto pinned = _call->videoEndpointPinned();
  1169. const auto camera = VideoEndpoint{
  1170. VideoEndpointType::Camera,
  1171. participantPeer,
  1172. computeCameraEndpoint(participant),
  1173. };
  1174. const auto screen = VideoEndpoint{
  1175. VideoEndpointType::Screen,
  1176. participantPeer,
  1177. computeScreenEndpoint(participant),
  1178. };
  1179. if (shown.contains(camera)) {
  1180. if (pinned && large == camera) {
  1181. result->addAction(
  1182. tr::lng_group_call_context_unpin_camera(tr::now),
  1183. [=] { _call->pinVideoEndpoint({}); });
  1184. } else {
  1185. result->addAction(
  1186. tr::lng_group_call_context_pin_camera(tr::now),
  1187. [=] { _call->pinVideoEndpoint(camera); });
  1188. }
  1189. }
  1190. if (shown.contains(screen)) {
  1191. if (pinned && large == screen) {
  1192. result->addAction(
  1193. tr::lng_group_call_context_unpin_screen(tr::now),
  1194. [=] { _call->pinVideoEndpoint({}); });
  1195. } else {
  1196. result->addAction(
  1197. tr::lng_group_call_context_pin_screen(tr::now),
  1198. [=] { _call->pinVideoEndpoint(screen); });
  1199. }
  1200. }
  1201. }
  1202. if (_call->rtmp()) {
  1203. addMuteActionsToContextMenu(
  1204. result,
  1205. row->peer(),
  1206. false,
  1207. static_cast<Row*>(row.get()));
  1208. } else if (participant
  1209. && (!isMe(participantPeer) || _peer->canManageGroupCall())
  1210. && (participant->ssrc != 0
  1211. || GetAdditionalAudioSsrc(participant->videoParams) != 0)) {
  1212. addMuteActionsToContextMenu(
  1213. result,
  1214. participantPeer,
  1215. admin,
  1216. static_cast<Row*>(row.get()));
  1217. }
  1218. }
  1219. if (isMe(participantPeer)) {
  1220. if (_call->muted() == MuteState::RaisedHand) {
  1221. const auto removeHand = [=] {
  1222. if (_call->muted() == MuteState::RaisedHand) {
  1223. _call->setMutedAndUpdate(MuteState::ForceMuted);
  1224. }
  1225. };
  1226. result->addAction(
  1227. tr::lng_group_call_context_remove_hand(tr::now),
  1228. removeHand);
  1229. }
  1230. } else {
  1231. result->addAction(
  1232. (participantPeer->isUser()
  1233. ? tr::lng_context_view_profile(tr::now)
  1234. : participantPeer->isBroadcast()
  1235. ? tr::lng_context_view_channel(tr::now)
  1236. : tr::lng_context_view_group(tr::now)),
  1237. showProfile);
  1238. if (participantPeer->isUser()) {
  1239. result->addAction(
  1240. tr::lng_context_send_message(tr::now),
  1241. showHistory);
  1242. }
  1243. const auto canKick = [&] {
  1244. const auto user = participantPeer->asUser();
  1245. if (static_cast<Row*>(row.get())->state()
  1246. == Row::State::Invited) {
  1247. return false;
  1248. } else if (const auto chat = _peer->asChat()) {
  1249. return chat->amCreator()
  1250. || (user
  1251. && chat->canBanMembers()
  1252. && !chat->admins.contains(user));
  1253. } else if (const auto channel = _peer->asChannel()) {
  1254. return !participantPeer->isMegagroup() // That's the creator.
  1255. && channel->canRestrictParticipant(participantPeer);
  1256. }
  1257. return false;
  1258. }();
  1259. if (canKick) {
  1260. result->addAction(MakeAttentionAction(
  1261. result->menu(),
  1262. tr::lng_group_call_context_remove(tr::now),
  1263. removeFromVoiceChat));
  1264. }
  1265. }
  1266. if (result->actions().size() < (addCover ? 2 : 1)) {
  1267. return nullptr;
  1268. }
  1269. return result;
  1270. }
  1271. void Members::Controller::addMuteActionsToContextMenu(
  1272. not_null<Ui::PopupMenu*> menu,
  1273. not_null<PeerData*> participantPeer,
  1274. bool participantIsCallAdmin,
  1275. not_null<Row*> row) {
  1276. const auto muteUnmuteString = [=](bool muted, bool mutedByMe) {
  1277. return (muted && _peer->canManageGroupCall())
  1278. ? tr::lng_group_call_context_unmute(tr::now)
  1279. : mutedByMe
  1280. ? tr::lng_group_call_context_unmute_for_me(tr::now)
  1281. : _peer->canManageGroupCall()
  1282. ? tr::lng_group_call_context_mute(tr::now)
  1283. : tr::lng_group_call_context_mute_for_me(tr::now);
  1284. };
  1285. const auto toggleMute = crl::guard(this, [=](bool mute, bool local) {
  1286. _toggleMuteRequests.fire(Group::MuteRequest{
  1287. .peer = participantPeer,
  1288. .mute = mute,
  1289. .locallyOnly = local,
  1290. });
  1291. });
  1292. const auto changeVolume = crl::guard(this, [=](
  1293. int volume,
  1294. bool local) {
  1295. _changeVolumeRequests.fire(Group::VolumeRequest{
  1296. .peer = participantPeer,
  1297. .volume = std::clamp(volume, 1, Group::kMaxVolume),
  1298. .locallyOnly = local,
  1299. });
  1300. });
  1301. const auto muteState = row->state();
  1302. const auto muted = (muteState == Row::State::Muted)
  1303. || (muteState == Row::State::RaisedHand);
  1304. const auto mutedByMe = row->mutedByMe();
  1305. auto mutesFromVolume = rpl::never<bool>() | rpl::type_erased();
  1306. const auto addVolumeItem = (!muted || isMe(participantPeer));
  1307. if (addVolumeItem) {
  1308. auto otherParticipantStateValue
  1309. = _call->otherParticipantStateValue(
  1310. ) | rpl::filter([=](const Group::ParticipantState &data) {
  1311. return data.peer == participantPeer;
  1312. });
  1313. auto volumeItem = base::make_unique_q<MenuVolumeItem>(
  1314. menu->menu(),
  1315. st::groupCallPopupVolumeMenu,
  1316. st::groupCallMenuVolumeSlider,
  1317. otherParticipantStateValue,
  1318. _call->rtmp() ? _call->rtmpVolume() : row->volume(),
  1319. Group::kMaxVolume,
  1320. muted,
  1321. st::groupCallMenuVolumePadding);
  1322. mutesFromVolume = volumeItem->toggleMuteRequests();
  1323. volumeItem->toggleMuteRequests(
  1324. ) | rpl::start_with_next([=](bool muted) {
  1325. if (muted) {
  1326. // Slider value is changed after the callback is called.
  1327. // To capture good state inside the slider frame we postpone.
  1328. crl::on_main(menu, [=] {
  1329. menu->hideMenu();
  1330. });
  1331. }
  1332. toggleMute(muted, false);
  1333. }, volumeItem->lifetime());
  1334. volumeItem->toggleMuteLocallyRequests(
  1335. ) | rpl::start_with_next([=](bool muted) {
  1336. if (!isMe(participantPeer)) {
  1337. toggleMute(muted, true);
  1338. }
  1339. }, volumeItem->lifetime());
  1340. volumeItem->changeVolumeRequests(
  1341. ) | rpl::start_with_next([=](int volume) {
  1342. changeVolume(volume, false);
  1343. }, volumeItem->lifetime());
  1344. volumeItem->changeVolumeLocallyRequests(
  1345. ) | rpl::start_with_next([=](int volume) {
  1346. if (!isMe(participantPeer)) {
  1347. changeVolume(volume, true);
  1348. }
  1349. }, volumeItem->lifetime());
  1350. if (menu->actions().size() > 1) { // First - cover.
  1351. menu->addSeparator();
  1352. }
  1353. menu->addAction(std::move(volumeItem));
  1354. if (!_call->rtmp() && !isMe(participantPeer)) {
  1355. menu->addSeparator();
  1356. }
  1357. };
  1358. const auto muteAction = [&]() -> QAction* {
  1359. if (muteState == Row::State::Invited
  1360. || _call->rtmp()
  1361. || isMe(participantPeer)
  1362. || (muteState == Row::State::Inactive
  1363. && participantIsCallAdmin
  1364. && _peer->canManageGroupCall())) {
  1365. return nullptr;
  1366. }
  1367. auto callback = [=] {
  1368. const auto state = row->state();
  1369. const auto muted = (state == Row::State::Muted)
  1370. || (state == Row::State::RaisedHand);
  1371. const auto mutedByMe = row->mutedByMe();
  1372. toggleMute(!mutedByMe && (!_call->canManage() || !muted), false);
  1373. };
  1374. return menu->addAction(
  1375. muteUnmuteString(muted, mutedByMe),
  1376. std::move(callback));
  1377. }();
  1378. if (muteAction) {
  1379. std::move(
  1380. mutesFromVolume
  1381. ) | rpl::start_with_next([=](bool mutedFromVolume) {
  1382. const auto state = _call->canManage()
  1383. ? (mutedFromVolume
  1384. ? (row->raisedHandRating()
  1385. ? Row::State::RaisedHand
  1386. : Row::State::Muted)
  1387. : Row::State::Inactive)
  1388. : row->state();
  1389. const auto muted = (state == Row::State::Muted)
  1390. || (state == Row::State::RaisedHand);
  1391. const auto mutedByMe = _call->canManage()
  1392. ? false
  1393. : mutedFromVolume;
  1394. muteAction->setText(muteUnmuteString(muted, mutedByMe));
  1395. }, menu->lifetime());
  1396. }
  1397. }
  1398. std::unique_ptr<Row> Members::Controller::createRowForMe() {
  1399. auto result = std::make_unique<Row>(this, _call->joinAs());
  1400. updateRow(result.get(), std::nullopt, nullptr);
  1401. return result;
  1402. }
  1403. std::unique_ptr<Row> Members::Controller::createRow(
  1404. const Data::GroupCallParticipant &participant) {
  1405. auto result = std::make_unique<Row>(this, participant.peer);
  1406. updateRow(result.get(), std::nullopt, &participant);
  1407. return result;
  1408. }
  1409. std::unique_ptr<Row> Members::Controller::createInvitedRow(
  1410. not_null<PeerData*> participantPeer) {
  1411. if (findRow(participantPeer)) {
  1412. return nullptr;
  1413. }
  1414. auto result = std::make_unique<Row>(this, participantPeer);
  1415. updateRow(result.get(), std::nullopt, nullptr);
  1416. return result;
  1417. }
  1418. Members::Members(
  1419. not_null<QWidget*> parent,
  1420. not_null<GroupCall*> call,
  1421. PanelMode mode,
  1422. Ui::GL::Backend backend)
  1423. : RpWidget(parent)
  1424. , _call(call)
  1425. , _mode(mode)
  1426. , _scroll(this)
  1427. , _listController(std::make_unique<Controller>(call, parent, mode))
  1428. , _layout(_scroll->setOwnedWidget(
  1429. object_ptr<Ui::VerticalLayout>(_scroll.data())))
  1430. , _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
  1431. , _viewport(
  1432. std::make_unique<Viewport>(
  1433. _videoWrap.get(),
  1434. PanelMode::Default,
  1435. backend)) {
  1436. setupList();
  1437. setupAddMember(call);
  1438. setContent(_list);
  1439. setupFakeRoundCorners();
  1440. _listController->setDelegate(static_cast<PeerListDelegate*>(this));
  1441. trackViewportGeometry();
  1442. }
  1443. Members::~Members() {
  1444. _viewport = nullptr;
  1445. }
  1446. auto Members::toggleMuteRequests() const
  1447. -> rpl::producer<Group::MuteRequest> {
  1448. return _listController->toggleMuteRequests();
  1449. }
  1450. auto Members::changeVolumeRequests() const
  1451. -> rpl::producer<Group::VolumeRequest> {
  1452. return _listController->changeVolumeRequests();
  1453. }
  1454. auto Members::kickParticipantRequests() const
  1455. -> rpl::producer<not_null<PeerData*>> {
  1456. return _listController->kickParticipantRequests();
  1457. }
  1458. not_null<Viewport*> Members::viewport() const {
  1459. return _viewport.get();
  1460. }
  1461. int Members::desiredHeight() const {
  1462. const auto count = [&] {
  1463. if (const auto real = _call->lookupReal()) {
  1464. return real->fullCount();
  1465. }
  1466. return 0;
  1467. }();
  1468. const auto use = std::max(count, _list->fullRowsCount());
  1469. const auto single = st::groupCallMembersList.item.height;
  1470. const auto desired = (_layout->height() - _list->height())
  1471. + (use * single)
  1472. + (use ? st::lineWidth : 0);
  1473. return std::max(height(), desired);
  1474. }
  1475. rpl::producer<int> Members::desiredHeightValue() const {
  1476. return rpl::combine(
  1477. heightValue(),
  1478. _addMemberButton.value(),
  1479. _listController->fullCountValue(),
  1480. _mode.value()
  1481. ) | rpl::map([=] {
  1482. return desiredHeight();
  1483. });
  1484. }
  1485. void Members::setupAddMember(not_null<GroupCall*> call) {
  1486. using namespace rpl::mappers;
  1487. const auto peer = call->peer();
  1488. const auto canAddByPeer = [=](not_null<PeerData*> peer) {
  1489. if (peer->isBroadcast()) {
  1490. return rpl::single(false) | rpl::type_erased();
  1491. }
  1492. return rpl::combine(
  1493. Data::CanSendValue(peer, ChatRestriction::SendOther, false),
  1494. _call->joinAsValue()
  1495. ) | rpl::map([=](bool can, not_null<PeerData*> joinAs) {
  1496. return can && joinAs->isSelf();
  1497. }) | rpl::type_erased();
  1498. };
  1499. const auto canInviteByLinkByPeer = [=](not_null<PeerData*> peer) {
  1500. const auto channel = peer->asChannel();
  1501. if (!channel) {
  1502. return rpl::single(false) | rpl::type_erased();
  1503. }
  1504. return rpl::single(
  1505. false
  1506. ) | rpl::then(_call->real(
  1507. ) | rpl::map([=] {
  1508. return Data::PeerFlagValue(
  1509. channel,
  1510. ChannelDataFlag::Username);
  1511. }) | rpl::flatten_latest()) | rpl::type_erased();
  1512. };
  1513. _canAddMembers = canAddByPeer(peer);
  1514. _canInviteByLink = canInviteByLinkByPeer(peer);
  1515. SubscribeToMigration(
  1516. peer,
  1517. lifetime(),
  1518. [=](not_null<ChannelData*> channel) {
  1519. _canAddMembers = canAddByPeer(channel);
  1520. _canInviteByLink = canInviteByLinkByPeer(channel);
  1521. });
  1522. rpl::combine(
  1523. _canAddMembers.value(),
  1524. _canInviteByLink.value(),
  1525. _mode.value()
  1526. ) | rpl::start_with_next([=](bool add, bool invite, PanelMode mode) {
  1527. if (!add && !invite) {
  1528. if (const auto old = _addMemberButton.current()) {
  1529. delete old;
  1530. _addMemberButton = nullptr;
  1531. updateControlsGeometry();
  1532. }
  1533. return;
  1534. }
  1535. auto addMember = Settings::CreateButtonWithIcon(
  1536. _layout.get(),
  1537. tr::lng_group_call_invite(),
  1538. st::groupCallAddMember,
  1539. { .icon = &st::groupCallAddMemberIcon });
  1540. addMember->clicks(
  1541. ) | rpl::to_empty | rpl::start_to_stream(
  1542. _addMemberRequests,
  1543. addMember->lifetime());
  1544. addMember->show();
  1545. addMember->resizeToWidth(_layout->width());
  1546. delete _addMemberButton.current();
  1547. _addMemberButton = addMember.data();
  1548. _layout->insert(3, std::move(addMember));
  1549. }, lifetime());
  1550. updateControlsGeometry();
  1551. }
  1552. Row *Members::lookupRow(not_null<PeerData*> peer) const {
  1553. return _listController->findRow(peer);
  1554. }
  1555. not_null<MembersRow*> Members::rtmpFakeRow(not_null<PeerData*> peer) const {
  1556. if (!_rtmpFakeRow) {
  1557. _rtmpFakeRow = std::make_unique<Row>(_listController.get(), peer);
  1558. }
  1559. return _rtmpFakeRow.get();
  1560. }
  1561. void Members::setMode(PanelMode mode) {
  1562. if (_mode.current() == mode) {
  1563. return;
  1564. }
  1565. _mode = mode;
  1566. _listController->setMode(mode);
  1567. }
  1568. QRect Members::getInnerGeometry() const {
  1569. const auto addMembers = _addMemberButton.current();
  1570. const auto add = addMembers ? addMembers->height() : 0;
  1571. return QRect(
  1572. 0,
  1573. -_scroll->scrollTop(),
  1574. width(),
  1575. _list->y() + _list->height() + _bottomSkip->height() + add);
  1576. }
  1577. rpl::producer<int> Members::fullCountValue() const {
  1578. return _listController->fullCountValue();
  1579. }
  1580. void Members::setupList() {
  1581. _listController->setStyleOverrides(&st::groupCallMembersList);
  1582. const auto addSkip = [&] {
  1583. const auto result = _layout->add(
  1584. object_ptr<Ui::FixedHeightWidget>(
  1585. _layout.get(),
  1586. st::groupCallMembersTopSkip));
  1587. result->paintRequest(
  1588. ) | rpl::start_with_next([=](QRect clip) {
  1589. QPainter(result).fillRect(clip, st::groupCallMembersBg);
  1590. }, result->lifetime());
  1591. return result;
  1592. };
  1593. _topSkip = addSkip();
  1594. _list = _layout->add(
  1595. object_ptr<ListWidget>(
  1596. _layout.get(),
  1597. _listController.get()));
  1598. _bottomSkip = addSkip();
  1599. using namespace rpl::mappers;
  1600. rpl::combine(
  1601. _list->heightValue() | rpl::map(_1 > 0),
  1602. _addMemberButton.value() | rpl::map(_1 != nullptr)
  1603. ) | rpl::distinct_until_changed(
  1604. ) | rpl::start_with_next([=](bool hasList, bool hasAddMembers) {
  1605. _topSkip->resize(
  1606. _topSkip->width(),
  1607. hasList ? st::groupCallMembersTopSkip : 0);
  1608. _bottomSkip->resize(
  1609. _bottomSkip->width(),
  1610. (hasList && !hasAddMembers) ? st::groupCallMembersTopSkip : 0);
  1611. }, _list->lifetime());
  1612. const auto skip = _layout->add(object_ptr<Ui::RpWidget>(_layout.get()));
  1613. _mode.value(
  1614. ) | rpl::start_with_next([=](PanelMode mode) {
  1615. skip->resize(skip->width(), (mode == PanelMode::Default)
  1616. ? st::groupCallMembersBottomSkip
  1617. : 0);
  1618. }, skip->lifetime());
  1619. rpl::combine(
  1620. _mode.value(),
  1621. _layout->heightValue()
  1622. ) | rpl::start_with_next([=] {
  1623. resizeToList();
  1624. }, _layout->lifetime());
  1625. rpl::combine(
  1626. _scroll->scrollTopValue(),
  1627. _scroll->heightValue()
  1628. ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) {
  1629. _layout->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight);
  1630. }, _scroll->lifetime());
  1631. }
  1632. void Members::trackViewportGeometry() {
  1633. _call->videoEndpointLargeValue(
  1634. ) | rpl::start_with_next([=](const VideoEndpoint &large) {
  1635. _viewport->showLarge(large);
  1636. }, _viewport->lifetime());
  1637. const auto move = [=] {
  1638. const auto maxTop = _viewport->fullHeight()
  1639. - _viewport->widget()->height();
  1640. if (maxTop < 0) {
  1641. return;
  1642. }
  1643. const auto scrollTop = _scroll->scrollTop();
  1644. const auto shift = std::min(scrollTop, maxTop);
  1645. _viewport->setScrollTop(shift);
  1646. if (_viewport->widget()->y() != shift) {
  1647. _viewport->widget()->move(0, shift);
  1648. }
  1649. };
  1650. const auto resize = [=] {
  1651. _viewport->widget()->resize(
  1652. _layout->width(),
  1653. std::min(_scroll->height(), _viewport->fullHeight()));
  1654. };
  1655. _layout->widthValue(
  1656. ) | rpl::start_with_next([=](int width) {
  1657. _viewport->resizeToWidth(width);
  1658. resize();
  1659. }, _viewport->lifetime());
  1660. _scroll->heightValue(
  1661. ) | rpl::skip(1) | rpl::start_with_next(resize, _viewport->lifetime());
  1662. _scroll->scrollTopValue(
  1663. ) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
  1664. _viewport->fullHeightValue(
  1665. ) | rpl::start_with_next([=](int viewport) {
  1666. _videoWrap->resize(_videoWrap->width(), viewport);
  1667. if (viewport > 0) {
  1668. move();
  1669. resize();
  1670. }
  1671. }, _viewport->lifetime());
  1672. }
  1673. void Members::resizeEvent(QResizeEvent *e) {
  1674. updateControlsGeometry();
  1675. }
  1676. void Members::resizeToList() {
  1677. if (!_list) {
  1678. return;
  1679. }
  1680. const auto newHeight = (_list->height() > 0)
  1681. ? (_layout->height() + st::lineWidth)
  1682. : 0;
  1683. if (height() == newHeight) {
  1684. updateControlsGeometry();
  1685. } else {
  1686. resize(width(), newHeight);
  1687. }
  1688. }
  1689. void Members::updateControlsGeometry() {
  1690. _scroll->setGeometry(rect());
  1691. _layout->resizeToWidth(width());
  1692. }
  1693. void Members::setupFakeRoundCorners() {
  1694. const auto size = st::roundRadiusLarge;
  1695. const auto full = 3 * size;
  1696. const auto imagePartSize = size * style::DevicePixelRatio();
  1697. const auto imageSize = full * style::DevicePixelRatio();
  1698. const auto image = std::make_shared<QImage>(
  1699. QImage(imageSize, imageSize, QImage::Format_ARGB32_Premultiplied));
  1700. image->setDevicePixelRatio(style::DevicePixelRatio());
  1701. const auto refreshImage = [=] {
  1702. image->fill(st::groupCallBg->c);
  1703. {
  1704. QPainter p(image.get());
  1705. PainterHighQualityEnabler hq(p);
  1706. p.setCompositionMode(QPainter::CompositionMode_Source);
  1707. p.setPen(Qt::NoPen);
  1708. p.setBrush(Qt::transparent);
  1709. p.drawRoundedRect(0, 0, full, full, size, size);
  1710. }
  1711. };
  1712. const auto create = [&](QPoint imagePartOrigin) {
  1713. const auto result = Ui::CreateChild<Ui::RpWidget>(_layout.get());
  1714. result->show();
  1715. result->resize(size, size);
  1716. result->setAttribute(Qt::WA_TransparentForMouseEvents);
  1717. result->paintRequest(
  1718. ) | rpl::start_with_next([=] {
  1719. QPainter(result).drawImage(
  1720. result->rect(),
  1721. *image,
  1722. QRect(imagePartOrigin, QSize(imagePartSize, imagePartSize)));
  1723. }, result->lifetime());
  1724. result->raise();
  1725. return result;
  1726. };
  1727. const auto shift = imageSize - imagePartSize;
  1728. const auto topleft = create({ 0, 0 });
  1729. const auto topright = create({ shift, 0 });
  1730. const auto bottomleft = create({ 0, shift });
  1731. const auto bottomright = create({ shift, shift });
  1732. rpl::combine(
  1733. _list->geometryValue(),
  1734. _addMemberButton.value() | rpl::map([=](Ui::RpWidget *widget) {
  1735. topleft->raise();
  1736. topright->raise();
  1737. bottomleft->raise();
  1738. bottomright->raise();
  1739. return widget ? widget->heightValue() : rpl::single(0);
  1740. }) | rpl::flatten_latest()
  1741. ) | rpl::start_with_next([=](QRect list, int addMembers) {
  1742. const auto left = list.x();
  1743. const auto top = list.y() - _topSkip->height();
  1744. const auto right = left + list.width() - topright->width();
  1745. const auto bottom = top
  1746. + _topSkip->height()
  1747. + list.height()
  1748. + _bottomSkip->height()
  1749. + addMembers
  1750. - bottomleft->height();
  1751. topleft->move(left, top);
  1752. topright->move(right, top);
  1753. bottomleft->move(left, bottom);
  1754. bottomright->move(right, bottom);
  1755. }, lifetime());
  1756. refreshImage();
  1757. style::PaletteChanged(
  1758. ) | rpl::start_with_next([=] {
  1759. refreshImage();
  1760. topleft->update();
  1761. topright->update();
  1762. bottomleft->update();
  1763. bottomright->update();
  1764. }, lifetime());
  1765. }
  1766. void Members::peerListSetTitle(rpl::producer<QString> title) {
  1767. }
  1768. void Members::peerListSetAdditionalTitle(rpl::producer<QString> title) {
  1769. }
  1770. void Members::peerListSetHideEmpty(bool hide) {
  1771. }
  1772. bool Members::peerListIsRowChecked(not_null<PeerListRow*> row) {
  1773. return false;
  1774. }
  1775. void Members::peerListScrollToTop() {
  1776. }
  1777. int Members::peerListSelectedRowsCount() {
  1778. return 0;
  1779. }
  1780. void Members::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
  1781. Unexpected("Item selection in Calls::Members.");
  1782. }
  1783. void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
  1784. Unexpected("Item selection in Calls::Members.");
  1785. }
  1786. void Members::peerListFinishSelectedRowsBunch() {
  1787. }
  1788. void Members::peerListSetDescription(
  1789. object_ptr<Ui::FlatLabel> description) {
  1790. description.destroy();
  1791. }
  1792. std::shared_ptr<Main::SessionShow> Members::peerListUiShow() {
  1793. Unexpected("...Members::peerListUiShow");
  1794. }
  1795. } // namespace Calls::Group